This commit is contained in:
@@ -209,8 +209,11 @@ pnpm install
|
|||||||
# Missing dependencies exit 42; stale/foreign apps/web/.next state exits 43.
|
# Missing dependencies exit 42; stale/foreign apps/web/.next state exits 43.
|
||||||
# The web build certifies its exact standalone symlink manifest; added, removed,
|
# The web build certifies its exact standalone symlink manifest; added, removed,
|
||||||
# retargeted, or manifest-only-tampered generated links also exit 43. This detects
|
# retargeted, or manifest-only-tampered generated links also exit 43. This detects
|
||||||
# accidental/independent drift, not a same-UID actor that can rewrite both records
|
# accidental, independent, stale, and foreign-residue mutation—the class exposed by
|
||||||
# consistently (CWE-345); an external trust anchor is required for that boundary.
|
# a five-month-stale .next that produced 19 phantom TS2307 errors.
|
||||||
|
# It does NOT defend against a same-UID actor that can rewrite both manifest and
|
||||||
|
# marker consistently (CWE-345). RM-59 tracks the required executor/spine-side
|
||||||
|
# trust anchor outside worktree authority.
|
||||||
pnpm preflight
|
pnpm preflight
|
||||||
|
|
||||||
# Optional local queue service only. This does not start PostgreSQL.
|
# Optional local queue service only. This does not start PostgreSQL.
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
- The original blanket symlink wording conflicts with AC4 because canonical Next `output: 'standalone'` emits legitimate pnpm dependency symlinks. The coordinator independently verified 42 such links and approved the operative restatement: `.next` itself must not be a symlink; descendant symlinks must exactly match the successful build's certified manifest.
|
- The original blanket symlink wording conflicts with AC4 because canonical Next `output: 'standalone'` emits legitimate pnpm dependency symlinks. The coordinator independently verified 42 such links and approved the operative restatement: `.next` itself must not be a symlink; descendant symlinks must exactly match the successful build's certified manifest.
|
||||||
- RED-first controls were observed failing together against the prior implementation (exit 1): `.next` root, added, removed, retargeted, tampered-manifest, and canonical-style certified-link cases. The build now publishes the manifest atomically before the existing source certification commit marker; that marker binds the manifest SHA-256. Missing/partial/modified manifests remain untrusted.
|
- RED-first controls were observed failing together against the prior implementation (exit 1): `.next` root, added, removed, retargeted, tampered-manifest, and canonical-style certified-link cases. The build now publishes the manifest atomically before the existing source certification commit marker; that marker binds the manifest SHA-256. Missing/partial/modified manifests remain untrusted.
|
||||||
- GREEN evidence: the six-case symlink control passes; the exact reviewer-added link exits 43; removing it restores preflight exit 0. The added RED-first build-publication control also proves a symlinked `.next` cannot redirect certification writes outside the checkout. `pnpm test:checkout` passes 23 top-level tests / 29 including subtests. Canonical `pnpm --filter @mosaicstack/web build` and the following `pnpm preflight` both exit 0.
|
- GREEN evidence: the six-case symlink control passes; the exact reviewer-added link exits 43; removing it restores preflight exit 0. The added RED-first build-publication control also proves a symlinked `.next` cannot redirect certification writes outside the checkout. `pnpm test:checkout` passes 23 top-level tests / 29 including subtests. Canonical `pnpm --filter @mosaicstack/web build` and the following `pnpm preflight` both exit 0.
|
||||||
- Threat-model ruling: the manifest detects accidental, independent, and stale mutation only. It does not defend against a same-UID actor able to rewrite both manifest and marker consistently (CWE-345); no local worktree construction can without an external trust anchor. The residual belongs to the planned choke-point executor / PostgreSQL spine where verification can occur outside worktree authority.
|
- Threat-model ruling: the manifest detects accidental, independent, stale, and foreign-residue mutation—the class exposed by the five-month-stale `.next` that produced 19 phantom TS2307 errors. It does not defend against a same-UID actor able to rewrite both manifest and marker consistently (CWE-345); no local worktree construction can without an external trust anchor. RM-59 tracks the residual: executor/spine-side attestation outside worktree authority, dependent on RM-12, RM-21, and RM-25.
|
||||||
- AC8 concrete proof at `df7530ae`: a clean clone ran in `ci-base:latest` with Docker `--read-only`; its only writable mounts were `/workspace` (the worktree) and `/home/ci` (`HOME`, with `NPM_CONFIG_STORE_DIR=/home/ci/store`). `pnpm install --frozen-lockfile && pnpm -w typecheck` exited 0 with 45/45 uncached tasks. This proves the implemented checkout path requires no writable location outside `$HOME` and the worktree. An initial fixture attempt failed only because Git required `/workspace` safe-directory setup; it is not counted as evidence.
|
- AC8 concrete proof at `df7530ae`: a clean clone ran in `ci-base:latest` with Docker `--read-only`; its only writable mounts were `/workspace` (the worktree) and `/home/ci` (`HOME`, with `NPM_CONFIG_STORE_DIR=/home/ci/store`). `pnpm install --frozen-lockfile && pnpm -w typecheck` exited 0 with 45/45 uncached tasks. This proves the implemented checkout path requires no writable location outside `$HOME` and the worktree. An initial fixture attempt failed only because Git required `/workspace` safe-directory setup; it is not counted as evidence.
|
||||||
|
|
||||||
## Handoff
|
## Handoff
|
||||||
|
|||||||
+11
-7
@@ -164,11 +164,11 @@ export async function runPreflight({ root = process.cwd(), uid = process.getuid?
|
|||||||
let generated = [];
|
let generated = [];
|
||||||
try {
|
try {
|
||||||
const nextStats = await lstat(nextDir);
|
const nextStats = await lstat(nextDir);
|
||||||
if (nextStats.isSymbolicLink()) {
|
if (!nextStats.isDirectory() || nextStats.isSymbolicLink()) {
|
||||||
return {
|
return {
|
||||||
code: GENERATED_STATE_EXIT,
|
code: GENERATED_STATE_EXIT,
|
||||||
message:
|
message:
|
||||||
'MOSAIC_PREFLIGHT_GENERATED_STATE: apps/web/.next contains a symbolic link and is not trustworthy; run pnpm clean:generated, then rerun the gate',
|
'MOSAIC_PREFLIGHT_GENERATED_STATE: apps/web/.next must be a real directory, not a symbolic link, and is not trustworthy; run pnpm clean:generated, then rerun the gate',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
generated = [nextDir, ...(await entries(nextDir))];
|
generated = [nextDir, ...(await entries(nextDir))];
|
||||||
@@ -183,11 +183,15 @@ export async function runPreflight({ root = process.cwd(), uid = process.getuid?
|
|||||||
if (uid !== undefined && stats.uid !== uid) foreign.push(path.relative(root, target));
|
if (uid !== undefined && stats.uid !== uid) foreign.push(path.relative(root, target));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Threat model: this detects accidental, independent, or stale generated-state
|
// Detects accidental, independent, stale, and foreign-residue mutation of
|
||||||
// mutation. It does NOT defend against an actor with same-UID write access to
|
// generated state: the class this check was born from was a five-month-stale
|
||||||
// the generated tree: that actor can regenerate both the manifest and marker
|
// .next whose validator referenced deleted pages and produced 19 phantom TS2307
|
||||||
// consistently (CWE-345). No local construction can without a trust anchor
|
// errors indistinguishable from real type errors.
|
||||||
// outside that actor's authority.
|
//
|
||||||
|
// Does NOT defend against an actor with same-UID write access to the generated
|
||||||
|
// tree, which can regenerate both the manifest and marker consistently
|
||||||
|
// (CWE-345). No local construction can, absent a trust anchor outside that
|
||||||
|
// actor's authority. RM-59 tracks executor/spine-side attestation.
|
||||||
let certification = null;
|
let certification = null;
|
||||||
let certifiedManifest = null;
|
let certifiedManifest = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -128,6 +128,17 @@ test('generated-state symbolic links are accepted only when exactly build-certif
|
|||||||
assert.match(result.message, /symbolic link/);
|
assert.match(result.message, /symbolic link/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await t.test('apps/web/.next is rejected when it is not a directory', async () => {
|
||||||
|
const root = await fixture('non-directory-next-root');
|
||||||
|
await installRequiredBins(root);
|
||||||
|
await writeFile(path.join(root, 'apps', 'web', '.next'), 'not a Next build\n');
|
||||||
|
|
||||||
|
const result = await runPreflight({ root });
|
||||||
|
assert.equal(result.code, 43);
|
||||||
|
assert.match(result.message, /MOSAIC_PREFLIGHT_GENERATED_STATE/);
|
||||||
|
assert.match(result.message, /real directory/);
|
||||||
|
});
|
||||||
|
|
||||||
await t.test('an added descendant symlink is rejected', async () => {
|
await t.test('an added descendant symlink is rejected', async () => {
|
||||||
const root = await fixture('symbolic-next-added');
|
const root = await fixture('symbolic-next-added');
|
||||||
await installRequiredBins(root);
|
await installRequiredBins(root);
|
||||||
|
|||||||
Reference in New Issue
Block a user