diff --git a/docs/TEST_INTEGRITY.md b/docs/TEST_INTEGRITY.md index 15699da..6a2bc8c 100644 --- a/docs/TEST_INTEGRITY.md +++ b/docs/TEST_INTEGRITY.md @@ -2858,3 +2858,29 @@ test_integrity_note: did_relax_any_assertion: false if_true_explain_spec_basis: "" ``` + +# Self-host active-pointer privacy correction (2026-10-01) + +```yaml +test_integrity_note: + change_type: new_tests + scenarios: + - id: SCN-runtime-metadata-invalid-active-pointer-target-privacy + given: "A bounded reader fixture has a fixed active symlink with an absolute host target or an escaping relative target" + when: "The no-argument reader observes runtime metadata and the complete result is serialized" + then: "The active pointer is refused with RUNTIME_METADATA_ACTIVE_POINTER_TARGET_INVALID and a null target; the raw target, fixture root, file URLs, and absolute host paths are absent while unrelated package, source-commit, candidate-consistency, and Console disk observations remain visible" + observable_outcomes: + - "Malformed active-pointer targets are not returned as display data" + - "Pointer refusal does not erase independent local metadata observations or imply authentication, loaded-memory, delivered-byte, or native authority" + - "All earlier eight runtime-metadata scenarios and their assertions remain unchanged and strict" + determinism_controls: + - "Fixed absolute and escaping symlink target strings; canonical temporary package and data-home fixtures; recursive JSON serialization" + anti_cheat_rationale: + prevents: + - "An invalid symlink target exposing host paths through an otherwise redacted metadata response" + - "A privacy test that checks only module identity while missing nested runtime fields" + - "Malformed runtime state suppressing unrelated useful observations or manufacturing authority" + relaxation: + did_relax_any_assertion: false + if_true_explain_spec_basis: "" +``` diff --git a/src/self-host/runtime-metadata.mjs b/src/self-host/runtime-metadata.mjs index 6d5a1d5..9a5bd4b 100644 --- a/src/self-host/runtime-metadata.mjs +++ b/src/self-host/runtime-metadata.mjs @@ -523,7 +523,7 @@ function readActivePointer(issues) { const version = match ? versionValue(match[1]) : null; if (!version) { issues.push(issue('RUNTIME_METADATA_ACTIVE_POINTER_TARGET_INVALID', 'runtime/active')); - return { status: 'refused', target: link.text, version: null }; + return { status: 'refused', target: null, version: null }; } return { status: 'observed', target: link.text, version }; } diff --git a/test/fleet/runtime-metadata.test.mjs b/test/fleet/runtime-metadata.test.mjs index c16bb63..dc663e6 100644 --- a/test/fleet/runtime-metadata.test.mjs +++ b/test/fleet/runtime-metadata.test.mjs @@ -643,6 +643,43 @@ test('SCN-runtime-metadata-partial-journal', async () => { } finally { cleanup(fixture); } }); +test('SCN-runtime-metadata-invalid-active-pointer-target-privacy', async () => { + const invalidTargets = [ + '/host/qa-private-runtime', + '../../../../outside/qa-private-runtime', + ]; + for (const target of invalidTargets) { + const fixture = makeFixture(); + try { + writeValidation(fixture, ALPHA2_COMMIT); + const active = join(fixture.dataHome, 'torch', 'runtime', 'active'); + rmSync(active); + symlinkSync(target, active); + + const { value } = await runReader(fixture); + const pointer = value.currentActiveRuntime.activePointer; + assert.equal(pointer.status, 'refused'); + assert.equal(pointer.target, null); + assert.equal(pointer.version, null); + assert.ok(value.currentActiveRuntime.issues.some((entry) => ( + entry.code === 'RUNTIME_METADATA_ACTIVE_POINTER_TARGET_INVALID' + ))); + assert.equal(value.readerModuleEvaluation.packageVersion.value, '0.1.0-alpha.2'); + assert.equal(value.readerModuleEvaluation.declaredSourceCommit.value, ALPHA2_COMMIT); + assert.equal(value.readerModuleEvaluation.candidateTree.consistency, 'match'); + assert.equal(value.currentConsoleDisk.status, 'observed'); + assert.equal(value.authenticationConfidence, 'unknown'); + assert.equal(value.loadedMemoryCodeDigest, null); + assert.equal(value.deliveredBytesProof, null); + assert.equal(value.nativeAuthority, null); + + const serialized = JSON.stringify(value); + assert.equal(serialized.includes(target), false); + assertNoAbsolutePathLeak(value, fixture.temporaryRoot); + } finally { cleanup(fixture); } + } +}); + test('SCN-runtime-metadata-read-only-no-native-authority', async () => { const fixture = makeFixture(); try {