fix(transport): close code-review gaps in the import trust gate + severity split (T19/T20)
Addresses the T20 security code-review findings:
- Gate template script + alarm Expression-trigger bodies too (they compile and
execute at the site), not just script bodies — EnumerateTrustGatedScripts now
extracts and vets the {"expression":"..."} C# via the same trust validator.
- Per-origin local-declaration exclusion: a helper declared in a template script
no longer suppresses a genuinely-missing reference of the same name in an
ApiMethod (a global set silently defeated the ApiMethod hard-blocker).
- Fail-closed: a ScriptTrustValidator throw on one pathological body is caught
and surfaced as a hard blocker FOR THAT script (apply) / Blocker row (preview)
instead of aborting the whole import.
- Corrected the misleading attribute-Value comment (typed literal, not a compiled
expression) and documented the Pass-0 fail-fast rationale.
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
+80
@@ -367,6 +367,86 @@ public sealed class SemanticValidatorImportTests : IDisposable
|
||||
Assert.Contains(ex.Errors, err => err.Contains("Process", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Apply_AlarmExpressionTriggerWithForbiddenApi_HardBlocks()
|
||||
{
|
||||
// #05-T20 (code-review fix): an alarm's Expression trigger compiles and
|
||||
// executes at the site, so a forbidden API in the trigger expression must
|
||||
// be caught at import — not just in template/shared/ApiMethod script bodies.
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
var t = new Template("AlarmedTank");
|
||||
t.Alarms.Add(new TemplateAlarm("Bad")
|
||||
{
|
||||
TriggerType = AlarmTriggerType.Expression,
|
||||
TriggerConfiguration = "{\"expression\":\"System.Diagnostics.Process.GetCurrentProcess() != null\"}",
|
||||
PriorityLevel = 1,
|
||||
});
|
||||
ctx.Templates.Add(t);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var sessionId = await ExportWipeAndLoadAsync();
|
||||
|
||||
SemanticValidationException ex = default!;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
|
||||
ex = await Assert.ThrowsAsync<SemanticValidationException>(() =>
|
||||
importer.ApplyAsync(sessionId,
|
||||
new List<ImportResolution>
|
||||
{
|
||||
new("Template", "AlarmedTank", ResolutionAction.Add, null),
|
||||
},
|
||||
user: "bob"));
|
||||
}
|
||||
|
||||
Assert.NotEmpty(ex.Errors);
|
||||
Assert.Contains(ex.Errors, err => err.Contains("Process", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Apply_ApiMethodReference_NotSuppressedByTemplateLocalFunction()
|
||||
{
|
||||
// #05-T19 (code-review fix): a local function declared in a TEMPLATE script
|
||||
// must not suppress a genuinely-missing reference of the same name in an
|
||||
// ApiMethod — the ApiMethod finding stays a HARD error. A pre-fix global
|
||||
// locallyDeclared set silently defeated this.
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
var t = new Template("Helper");
|
||||
t.Scripts.Add(new Commons.Entities.Templates.TemplateScript(
|
||||
"init",
|
||||
"decimal SharedHelper(decimal x) => x * 2; return SharedHelper(1m);"));
|
||||
ctx.Templates.Add(t);
|
||||
ctx.ApiMethods.Add(new Commons.Entities.InboundApi.ApiMethod(
|
||||
"Ingest",
|
||||
"var x = SharedHelper(); return x;"));
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var sessionId = await ExportWipeAndLoadAsync();
|
||||
|
||||
SemanticValidationException ex = default!;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
|
||||
ex = await Assert.ThrowsAsync<SemanticValidationException>(() =>
|
||||
importer.ApplyAsync(sessionId,
|
||||
new List<ImportResolution>
|
||||
{
|
||||
new("Template", "Helper", ResolutionAction.Add, null),
|
||||
new("ApiMethod", "Ingest", ResolutionAction.Add, null),
|
||||
},
|
||||
user: "bob"));
|
||||
}
|
||||
|
||||
Assert.NotEmpty(ex.Errors);
|
||||
Assert.Contains(ex.Errors, err => err.Contains("SharedHelper", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SemanticValidator_catches_alarm_trigger_type_mismatch_at_import()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user