Protect private bank configuration from drift reconciliation (Refs #152)

This commit is contained in:
2026-10-12 23:32:20 -04:00
parent 4e3df81786
commit d9fa15cdf6
23 changed files with 597 additions and 28 deletions
@@ -1,3 +1,4 @@
import { assertPublicFileTarget } from '../fleet/private-config.js';
import { db } from '../db/index.js';
import { blobSize, getFileBlob } from '../db/file-blobs.js';
import type { LaunchRecipePlan } from '../fleet/launch-recipes.js';
@@ -22,6 +23,7 @@ export function applyActivationFiles(
version: number | null;
})[];
for (const change of changes) {
assertPublicFileTarget(change.server, change.kind, change.rel_path);
if (change.kind !== 'script' && change.kind !== 'config' && change.kind !== 'plugin')
throw new Error('File activation requires a runtime file kind.');
recipe.files = recipe.files.filter(
+8 -1
View File
@@ -1,3 +1,4 @@
import { assertPublicFileTarget, isPrivateConfigPath } from '../fleet/private-config.js';
import { assertScalingOwnership } from '../autoscaling/ownership.js';
import { removalAuthorization } from '../autoscaling/authorization.js';
import { scalingIntent } from '../autoscaling/admission.js';
@@ -509,6 +510,7 @@ function filesFor(job: Job): FilePlan[] {
'id' | 'kind' | 'rel_path' | 'check_before' | 'expected_before_sha'
> & { content_hash: string | null }
>;
for (const row of rows) assertPublicFileTarget(job.server, row.kind, row.rel_path);
return rows.map((row) => ({
id: row.id,
path: posix.join(
@@ -532,7 +534,12 @@ function storedFiles(job: Job): FilePlan[] {
const row = db()
.prepare('SELECT files_json FROM native_provisioning WHERE deployment_id=?')
.get(job.id) as { files_json: string | null } | undefined;
return (JSON.parse(row?.files_json ?? '[]') as FilePlan[]).map(fileMetadata);
const files = (JSON.parse(row?.files_json ?? '[]') as FilePlan[]).map(fileMetadata);
if (files.some((file) => isPrivateConfigPath(file.path)))
throw new Error(
'Private bank configuration cannot be captured or changed by a file campaign.'
);
return files;
}
/** Agent receipts are the authoritative before images; do not read a controller mount. */