fix(security): import trust gate covers instance alarm-override trigger expressions — 5th call site is surface-complete (plan R2-05 T6)
This commit is contained in:
@@ -946,8 +946,11 @@ public sealed class BundleImporter : IBundleImporter
|
||||
/// <summary>
|
||||
/// Enumerates every executable C# surface a bundle carries that the
|
||||
/// trust gate must vet: non-Skip template scripts + their Expression-trigger
|
||||
/// bodies, template alarm Expression-trigger bodies, shared scripts, and
|
||||
/// ApiMethod scripts. Expression triggers compile and execute at the site
|
||||
/// bodies, template alarm Expression-trigger bodies, shared scripts,
|
||||
/// ApiMethod scripts, AND instance alarm-override trigger expressions
|
||||
/// (<c>InstanceAlarmOverrideDto.TriggerConfigurationOverride</c>). Expression
|
||||
/// triggers — template alarms AND the instance overrides that replace them in
|
||||
/// the flattened config — compile and execute at the site
|
||||
/// (`TriggerExpressionGlobals`), so they are a genuine trust surface, not just
|
||||
/// script bodies. A null <paramref name="resolutionMap"/> (preview time, before
|
||||
/// resolutions are chosen) gates everything. Yields <c>(kind, entityName,
|
||||
@@ -998,6 +1001,25 @@ public sealed class BundleImporter : IBundleImporter
|
||||
yield return ("ApiMethod", m.Name, m.Name, m.Script);
|
||||
}
|
||||
}
|
||||
foreach (var i in content.Instances)
|
||||
{
|
||||
if (IsSkipResolution(resolutionMap, "Instance", i.UniqueName)) continue;
|
||||
foreach (var o in i.AlarmOverrides)
|
||||
{
|
||||
// The DTO carries no trigger type (the type lives on the template
|
||||
// alarm), so gate ANY override config carrying an {"expression":...}
|
||||
// string body: if the overridden alarm is not Expression-triggered
|
||||
// the body never executes, but vetting it anyway is fail-safe — the
|
||||
// structured (HiLo/threshold) configs have no "expression" key, so
|
||||
// there is no false-positive channel.
|
||||
var expr = ExtractExpressionBody(o.TriggerConfigurationOverride);
|
||||
if (!string.IsNullOrEmpty(expr))
|
||||
{
|
||||
yield return ("Instance", i.UniqueName,
|
||||
$"{o.AlarmCanonicalName} (alarm trigger override expression)", expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1009,8 +1031,24 @@ public sealed class BundleImporter : IBundleImporter
|
||||
private static string? ExtractTriggerExpression(string? triggerType, string? triggerConfigJson)
|
||||
{
|
||||
if (triggerType is null
|
||||
|| !triggerType.Equals("Expression", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.IsNullOrWhiteSpace(triggerConfigJson))
|
||||
|| !triggerType.Equals("Expression", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return ExtractExpressionBody(triggerConfigJson);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the C# boolean expression from an <c>{"expression":"…"}</c> trigger
|
||||
/// configuration, WITHOUT a trigger-type guard — used where the caller has no
|
||||
/// trigger type to check (an instance alarm override; the type lives on the
|
||||
/// template alarm). A config carrying no <c>"expression"</c> string key (the
|
||||
/// structured HiLo/threshold shapes) or malformed JSON yields null — nothing to
|
||||
/// gate.
|
||||
/// </summary>
|
||||
private static string? ExtractExpressionBody(string? triggerConfigJson)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(triggerConfigJson))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user