fix(security): enforce template locks on native alarm source overrides at flatten and management-command level

Add IsLocked to ResolvedNativeAlarmSource; FlatteningService skips overrides on a
locked source (mirrors attribute/alarm lock rules); ManagementActor SetInstanceNativeAlarmSourceOverride
flattens the instance and rejects an override on a locked source (and rejects a
canonical name that does not resolve, preventing dangling overrides).

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-09 16:28:52 -04:00
parent ab6077708f
commit 57f7f65772
6 changed files with 109 additions and 3 deletions
@@ -885,6 +885,27 @@ public class ManagementActor : ReceiveActor
await EnforceSiteScopeForInstance(sp, user, cmd.InstanceId);
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
// Enforce template-level locks (and reject dangling overrides) before persisting.
// Flatten the instance (script compilation skipped — a lock check does not need it)
// and look up the resolved source by canonical name: a locked source cannot be
// overridden, and an unresolved name would create a dead override the flattener
// silently drops.
var pipeline = sp.GetRequiredService<IFlatteningPipeline>();
var flattenResult = await pipeline.FlattenAndValidateAsync(
cmd.InstanceId, default, validateScripts: false);
if (flattenResult.IsFailure)
throw new ManagementCommandException(
$"Cannot set native alarm source override: the instance could not be flattened ({flattenResult.Error}).");
var resolvedSource = flattenResult.Value.Configuration.NativeAlarmSources
.FirstOrDefault(s => string.Equals(s.CanonicalName, cmd.SourceCanonicalName, StringComparison.Ordinal));
if (resolvedSource == null)
throw new ManagementCommandException(
$"Native alarm source '{cmd.SourceCanonicalName}' does not resolve for this instance and cannot be overridden.");
if (resolvedSource.IsLocked)
throw new ManagementCommandException(
$"Native alarm source '{cmd.SourceCanonicalName}' is locked at the template level and cannot be overridden.");
var existing = await repo.GetNativeAlarmSourceOverrideAsync(cmd.InstanceId, cmd.SourceCanonicalName);
if (existing == null)
{