From d1c0bb6ff7144450363bf55e47043cba7fcdfa09 Mon Sep 17 00:00:00 2001 From: Dave Liu <7david12liu@gmail.com> Date: Sat, 1 Aug 2026 20:51:03 -0700 Subject: [PATCH 1/2] fix(release): contain child process diagnostics (#596) --- OPERATIONS_RUNBOOK.md | 4 +- docs/officers/PUBLISH_AND_CHECK.md | 51 +++++++---- scripts/netlify-release-build.js | 6 +- tests/test-artifact-safety.test.js | 130 ++++++++++++++++++++++++++++- 4 files changed, 167 insertions(+), 24 deletions(-) diff --git a/OPERATIONS_RUNBOOK.md b/OPERATIONS_RUNBOOK.md index 0d9d570..d046c1c 100644 --- a/OPERATIONS_RUNBOOK.md +++ b/OPERATIONS_RUNBOOK.md @@ -675,7 +675,9 @@ For expand-and-contract changes: The GitHub release workflow is now manual and exact-current-commit. After protected approval it rechecks `main`, the newest exact CI run, and the retained credential-free artifact before cloud authentication. It fails on missing protected configuration, uses a lockfile Firebase CLI, verifies reviewed Rules and two named profile Functions before publishing the Pages branch, and never gives cloud authority to website preparation or publication. Future Pages artifacts omit the `runmprc.com` CNAME, but the existing Pages provider setting still claims that Netlify-served name until #136/WEB-001 publish and read it back. The source gate is not usable until #133 configures protected environments/OIDC. It does not publish the live Netlify host. Treat the full pipeline as incomplete until #105 closes. -WEB-PRIVACY-001AA [#582](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/582) changes only the executable Netlify release builder's outer caught-failure output. Source now uses an unbound catch, prints fixed `netlify_release_build_failed`, and preserves the failing exit. Synthetic black-box tests cover ordinary message detail, a hostile message getter, and a hostile prototype trap before any provider or release work can start. This proves only that the wrapper does not inspect or echo its caught value. The child-process runner and two Git reads still inherit subprocess diagnostics, so the complete build log is not claimed sanitized. The success evidence line, cleanup, authorization, build, artifact, and publication behavior are unchanged. Source, tests, and any merge do not prove a Netlify publication, website change, `runmprc.com` verification, Firebase deployment, provider configuration, production-data change, or live behavior; the fixed code says only that preparation failed. +WEB-PRIVACY-001AA [#582](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/582) changes only the executable Netlify release builder's outer caught-failure output. Source now uses an unbound catch, prints fixed `netlify_release_build_failed`, and preserves the failing exit. Synthetic black-box tests cover ordinary message detail, a hostile message getter, and a hostile prototype trap before any provider or release work can start. This proves only that the wrapper does not inspect or echo its caught value. At the #582 boundary, the child-process runner and two Git reads still inherited subprocess diagnostics; #596 below closes that separate residual inside the builder. The success evidence line, cleanup, authorization, build, artifact, and publication behavior are unchanged. Source, tests, and any merge do not prove a Netlify publication, website change, `runmprc.com` verification, Firebase deployment, provider configuration, production-data change, or live behavior; the fixed code says only that preparation failed. + +WEB-PRIVACY-001AB [#596](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/596) closes that named child-output residual inside the executable builder. Generic Git, npm, tar, and frontend-build commands now ignore child stdin, stdout, and stderr. The two Git identity reads capture only the required commit or tree on stdout and discard stderr. Synthetic local executables prove that ordinary exit failure, signal termination, `rev-parse` failure, `show` failure, and invalid captured identity values leave parent stdout empty and produce only fixed `netlify_release_build_failed` on parent stderr. This intentionally removes child progress and diagnostic detail. It preserves failure detection, exact Git comparison, cleanup, artifacts, and the parent-owned final `Prepared …` evidence. It does not sanitize Netlify platform output outside this process. Source, tests, and any merge still prove no website publication, `runmprc.com` verification, Firebase deployment, Netlify/provider configuration, production-data change, or live behavior. WEB-UX-001A [#457](https://github.com/Run-MPRC/Run-MPRC.github.io/issues/457) used a separate temporary **web-only** Netlify release. Its manifest named the expected first parent of one `main` merge, release-specific source ref, exact reviewed source commit/tree, exact artifact file count/digest, and known rollback deploy. The release-control Deploy Preview and production both built that pinned source. Netlify continued production only for the exact-parent merge, fetched the public ref into a fresh temporary Git repository with provider/global Git configuration disabled, verified both source hashes, installed the pinned lockfile with lifecycle scripts disabled, built without the data-dependent sitemap prebuild or any React/provider variables, scanned the output for credential markers, verified the exact artifact digest, and published a public provenance marker. A wrong/missing context, branch, commit, parent, ref, tree, artifact, canonical manifest, build-hook marker, or inactive manifest stops without replacing the live deploy. This path changed no Firebase, Rules, Functions, provider configuration, or production data and did not complete WEB-001. diff --git a/docs/officers/PUBLISH_AND_CHECK.md b/docs/officers/PUBLISH_AND_CHECK.md index ea050d7..021a812 100644 --- a/docs/officers/PUBLISH_AND_CHECK.md +++ b/docs/officers/PUBLISH_AND_CHECK.md @@ -63,30 +63,45 @@ As of **2026-07-13**, with the internal tooling note below checked from source o ### Netlify release-build failure output — SOURCE ONLY, NOT LIVE -**Purpose:** confirm that the final source wrapper reports a failed release preparation with one fixed code. The code does not say why preparation failed. It does not prove that Netlify published anything. +**Purpose:** confirm that the final source wrapper reports a failed release preparation with one fixed code and does not pass through child-command output. The code does not say why preparation failed. It does not prove that Netlify published anything. **Approver:** platform owner plus security reviewer. This is not an officer-operated release control. -**Prerequisites:** issue #582; one exact pull request and commit; synthetic tests with made-up canaries; the inactive temporary Netlify manifest; a reviewed undo; and confirmation that nobody ran a production release or contacted a provider to test this source change. +**Prerequisites:** issues #582 and #596; one exact pull request and commit for each source boundary; synthetic tests with made-up canaries and local fake commands; the inactive temporary Netlify manifest; a reviewed undo; and confirmation that nobody ran a production release or contacted a provider to test either source change. -1. Ask the platform maintainer for the exact issue, pull request, and commit. -2. Confirm the failure tests used only made-up values. -3. Confirm each synthetic failure stopped before release or provider work. -4. Read the redacted test summary. -5. Confirm the wrapper's complete stderr line is `netlify_release_build_failed`. -6. Confirm the subprocess exit was unsuccessful. -7. Confirm no thrown message, stack, path, URL, token-shaped canary, or trap canary appeared. -8. Confirm the full artifact-safety and release-workflow checks passed. -9. Confirm review found the existing cleanup and successful evidence line unchanged. -10. Record source, tests, merge, Netlify publication, website publication, `runmprc.com` verification, Firebase, provider, production data, and live behavior as separate states. - -**Expected result:** the outer wrapper does not read or echo its caught value. The fixed code means only that preparation failed. Child commands still write their own output, so this check does not prove that the complete Netlify build log is safe. - -**Stop conditions:** stop if anyone asks for a real credential, private provider value, production run, or raw log. Stop if output contains a thrown value, stack, path, URL, token-shaped value, or private data. Stop if anyone calls the whole build log sanitized or treats the fixed code as publication proof. +```mermaid +flowchart LR + Child["Git, npm, tar, or frontend-build command"] --> Hidden["Discard child output"] + Hidden --> Result{"Child succeeds?"} + Result -- "No" --> Fixed["Parent writes the fixed failure code and stops"] + Result -- "Yes" --> Evidence["Parent may write final prepared evidence"] + Outside["Netlify platform output outside this process"] -. "Not covered" .-> Separate["Separate provider boundary"] +``` -**Success proof:** keep the old-source failure summary, green synthetic subprocess results, complete artifact-safety and release-workflow results, reviewed commit, and separate statements for every delivery state. State plainly that Netlify, the website, `runmprc.com`, Firebase, outside providers, production data, and live behavior were not changed or verified unless each has separate dated proof. +In words: the builder discards command output; a command failure leaves only the fixed parent code, while success may produce the existing final parent evidence. Netlify platform output outside the builder remains separate. -**Undo:** use one reviewed revert or safe roll-forward pull request. Never restore raw caught-error logging. Never rerun production only to discover the cause. +1. Ask the platform maintainer for the exact issues, pull requests, and commits. +2. Confirm the failure tests used only made-up values. +3. Confirm the child-output tests used local fake commands and no network. +4. Confirm the fake generic command covered both exit failure and signal termination. +5. Confirm the two fake Git reads covered `rev-parse` and `show` failures. +6. Confirm each synthetic failure stopped before release or provider work. +7. Read the redacted test summary. +8. Confirm every failed wrapper's complete stderr line is `netlify_release_build_failed`. +9. Confirm every failed wrapper had empty stdout and an unsuccessful status. +10. Confirm no child output, thrown message, stack, path, URL, token-shaped canary, or trap canary appeared. +11. Confirm source contains no inherited child-output setting. +12. Confirm the full artifact-safety and release-workflow checks passed. +13. Confirm review found exact Git comparison, cleanup, artifacts, and the successful parent evidence line unchanged. +14. Record source, tests, merge, Netlify publication, website publication, `runmprc.com` verification, Firebase, provider, production data, and live behavior as separate states. + +**Expected result:** the outer wrapper does not read or echo its caught value. Child commands write no bytes to the wrapper's output. Required Git commit and tree values remain internal. A failure leaves only the fixed parent code. The code means only that preparation failed. This check does not cover Netlify platform output outside the builder or prove that the complete provider log is safe. + +**Stop conditions:** stop if anyone asks for a real credential, private provider value, production run, or raw log. Stop if a failed wrapper has stdout, more than the fixed stderr code, or a successful status. Stop if output contains child detail, a thrown value, stack, path, URL, token-shaped value, or private data. Stop if anyone calls the whole provider log sanitized or treats the fixed code as publication proof. + +**Success proof:** keep the old-source failure summaries, green synthetic wrapper and fake-command results, the no-inherited-output check, complete artifact-safety and release-workflow results, reviewed commits, and separate statements for every delivery state. State plainly that Netlify, the website, `runmprc.com`, Firebase, outside providers, production data, and live behavior were not changed or verified unless each has separate dated proof. + +**Undo:** use one reviewed revert or safe roll-forward pull request. Never restore raw caught-error logging or inherited child output. Never rerun production only to discover the cause. **Escalation:** contact the platform and security owners. If an earlier log may contain a sensitive value, use the private incident path. Do not copy the value into GitHub, a screenshot, email, or an AI tool. diff --git a/scripts/netlify-release-build.js b/scripts/netlify-release-build.js index 54c959a..8038352 100644 --- a/scripts/netlify-release-build.js +++ b/scripts/netlify-release-build.js @@ -51,7 +51,7 @@ function buildEnvironment(home, sourceEnvironment = process.env) { function run(command, args, options = {}) { execFileSync(command, args, { ...options, - stdio: 'inherit', + stdio: 'ignore', }); } @@ -92,7 +92,7 @@ function fetchAndVerifySource(manifest, temporaryDirectory) { cwd: temporaryDirectory, encoding: 'utf8', env: gitEnvironment, - stdio: ['ignore', 'pipe', 'inherit'], + stdio: ['ignore', 'pipe', 'ignore'], }, ).trim(); const fetchedTree = execFileSync( @@ -102,7 +102,7 @@ function fetchAndVerifySource(manifest, temporaryDirectory) { cwd: temporaryDirectory, encoding: 'utf8', env: gitEnvironment, - stdio: ['ignore', 'pipe', 'inherit'], + stdio: ['ignore', 'pipe', 'ignore'], }, ).trim(); if (fetchedCommit !== manifest.sourceCommit diff --git a/tests/test-artifact-safety.test.js b/tests/test-artifact-safety.test.js index e40a6ae..1acdca0 100644 --- a/tests/test-artifact-safety.test.js +++ b/tests/test-artifact-safety.test.js @@ -149,7 +149,7 @@ function runSitemapGenerator(fixture, environment = {}) { }); } -function makeNetlifyReleaseBuilderFixture(loadManifestBody) { +function makeNetlifyReleaseBuilderFixture(loadManifestBody, fakeGit = null) { const { root } = makeArtifactRoot('netlify-release-builder-fixture'); const script = path.join(root, 'scripts/netlify-release-build.js'); fs.mkdirSync(path.dirname(script), { recursive: true }); @@ -161,12 +161,41 @@ function makeNetlifyReleaseBuilderFixture(loadManifestBody) { + ` throw new Error('unused synthetic authorization');\n },\n` + ` loadManifest() {\n${loadManifestBody}\n },\n};\n`, ); - return { root, script }; + let binaryDirectory; + if (fakeGit) { + binaryDirectory = path.join(root, 'synthetic-bin'); + const fakeGitPath = writeArtifact( + root, + 'synthetic-bin/git', + `#!${process.execPath}\n'use strict';\n` + + `const args = process.argv.slice(2);\n` + + `const operation = ['init', 'fetch', 'rev-parse', 'show', 'archive']\n` + + ` .find((candidate) => args.includes(candidate)) || 'unknown';\n` + + `if (operation === ${JSON.stringify(fakeGit.failureStage)}) {\n` + + ` process.stdout.write(${JSON.stringify(fakeGit.stdoutCanary)});\n` + + ` process.stderr.write(${JSON.stringify(fakeGit.stderrCanary)});\n` + + (fakeGit.signal + ? ` process.kill(process.pid, ${JSON.stringify(fakeGit.signal)});\n` + : ' process.exit(23);\n') + + `}\n` + + `if (operation === 'rev-parse') {\n` + + ` process.stdout.write(${JSON.stringify(`${fakeGit.commit}\n`)});\n` + + `}\n` + + `if (operation === 'show') {\n` + + ` process.stdout.write(${JSON.stringify(`${fakeGit.tree}\n`)});\n` + + `}\n`, + ); + fs.chmodSync(fakeGitPath, 0o755); + } + return { binaryDirectory, root, script }; } function runNetlifyReleaseBuilder(fixture) { const env = { ...process.env }; delete env.NETLIFY; + if (fixture.binaryDirectory) { + env.PATH = `${fixture.binaryDirectory}${path.delimiter}${env.PATH}`; + } return spawnSync(process.execPath, [fixture.script, '--local'], { cwd: fixture.root, encoding: 'utf8', @@ -495,6 +524,103 @@ test('WEB-PRIVACY-001AA release-wrapper caught failures use one fixed code', asy } }); +test('WEB-PRIVACY-001AB release child diagnostics stay inside the process', async (t) => { + const fixedFailure = 'netlify_release_build_failed\n'; + const sourceCommit = 'a'.repeat(40); + const sourceTree = 'b'.repeat(40); + const pathCanary = '/synthetic/private/netlify-child-output-canary'; + const tokenCanary = ['s', 'k', '_', 'live', '_', 'C'.repeat(24)].join(''); + const authorization = JSON.stringify({ + ok: true, + manifest: { + active: true, + releaseId: 'synthetic-child-output-release', + sourceCommit, + sourceRef: 'refs/heads/synthetic-child-output', + sourceRepository: 'https://example.test/synthetic.git', + sourceTree, + }, + }); + const cases = [ + { + name: 'generic child exit output is discarded', + failureStage: 'init', + }, + { + name: 'generic child signal output is discarded', + failureStage: 'init', + signal: 'SIGTERM', + }, + { + name: 'rev-parse stderr is discarded', + failureStage: 'rev-parse', + }, + { + name: 'show stderr is discarded after required stdout capture', + failureStage: 'show', + }, + ]; + + for (const scenario of cases) { + await t.test(scenario.name, () => { + const stdoutCanary = `${pathCanary}-${scenario.failureStage}-stdout\n`; + const stderrCanary = `${tokenCanary}-${scenario.failureStage}-stderr\n`; + const fixture = makeNetlifyReleaseBuilderFixture( + ` return ${authorization};`, + { + commit: sourceCommit, + failureStage: scenario.failureStage, + signal: scenario.signal, + stderrCanary, + stdoutCanary, + tree: sourceTree, + }, + ); + const result = runNetlifyReleaseBuilder(fixture); + + assert.equal(result.error, undefined); + assert.equal(result.signal, null); + assert.equal(result.status, 1); + assert.equal(result.stdout, ''); + assert.equal(result.stderr, fixedFailure); + [pathCanary, tokenCanary].forEach((canary) => { + assert.doesNotMatch( + `${result.stdout}\n${result.stderr}`, + new RegExp(canary, 'u'), + ); + }); + }); + } + + await t.test('invalid captured Git values remain private and fail closed', () => { + const invalidCommit = 'captured-git-value-canary'; + const fixture = makeNetlifyReleaseBuilderFixture( + ` return ${authorization};`, + { + commit: invalidCommit, + failureStage: 'never', + stderrCanary: 'unused-stderr-canary', + stdoutCanary: 'unused-stdout-canary', + tree: sourceTree, + }, + ); + const result = runNetlifyReleaseBuilder(fixture); + + assert.equal(result.error, undefined); + assert.equal(result.signal, null); + assert.equal(result.status, 1); + assert.equal(result.stdout, ''); + assert.equal(result.stderr, fixedFailure); + assert.doesNotMatch(result.stderr, new RegExp(invalidCommit, 'u')); + }); + + await t.test('builder source contains no inherited child stdio', () => { + const source = fs.readFileSync(NETLIFY_RELEASE_BUILDER, 'utf8'); + assert.doesNotMatch(source, /stdio:\s*['"]inherit['"]/u); + assert.doesNotMatch(source, /['"]inherit['"]/u); + }); +}); + test('safe synthetic nested reports pass and source outside explicit roots is ignored', () => { const { root, temporaryDirectory } = makeArtifactRoot(); writeArtifact(root, 'nested/results.json', JSON.stringify({ From 85a4f626247a8cd311a7f96c6853c7a5b8afcd63 Mon Sep 17 00:00:00 2001 From: Dave Liu <7david12liu@gmail.com> Date: Sat, 1 Aug 2026 20:57:50 -0700 Subject: [PATCH 2/2] docs(officers): clarify release helper output flow (#596) --- docs/officers/PUBLISH_AND_CHECK.md | 37 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/officers/PUBLISH_AND_CHECK.md b/docs/officers/PUBLISH_AND_CHECK.md index 021a812..fb8e591 100644 --- a/docs/officers/PUBLISH_AND_CHECK.md +++ b/docs/officers/PUBLISH_AND_CHECK.md @@ -63,45 +63,48 @@ As of **2026-07-13**, with the internal tooling note below checked from source o ### Netlify release-build failure output — SOURCE ONLY, NOT LIVE -**Purpose:** confirm that the final source wrapper reports a failed release preparation with one fixed code and does not pass through child-command output. The code does not say why preparation failed. It does not prove that Netlify published anything. +**Purpose:** confirm that the final source wrapper reports a failed release preparation with one fixed code and does not pass through helper-program output. The code does not say why preparation failed. It does not prove that Netlify published anything. **Approver:** platform owner plus security reviewer. This is not an officer-operated release control. -**Prerequisites:** issues #582 and #596; one exact pull request and commit for each source boundary; synthetic tests with made-up canaries and local fake commands; the inactive temporary Netlify manifest; a reviewed undo; and confirmation that nobody ran a production release or contacted a provider to test either source change. +**Prerequisites:** issues #582 and #596; one exact pull request and commit for each source boundary; synthetic tests with made-up warning values and local helper programs; the inactive temporary Netlify manifest; a reviewed undo; and confirmation that nobody ran a production release or contacted a provider to test either source change. ```mermaid flowchart LR - Child["Git, npm, tar, or frontend-build command"] --> Hidden["Discard child output"] - Hidden --> Result{"Child succeeds?"} + Child["Git, npm, tar, or frontend-build helper"] --> Kind{"Fetched commit or file-tree check?"} + Kind -- "Yes" --> Internal["Keep only the required value inside the builder"] + Kind -- "No" --> Hidden["Discard normal and error output"] + Internal --> Result{"Helper succeeds?"} + Hidden --> Result Result -- "No" --> Fixed["Parent writes the fixed failure code and stops"] Result -- "Yes" --> Evidence["Parent may write final prepared evidence"] Outside["Netlify platform output outside this process"] -. "Not covered" .-> Separate["Separate provider boundary"] ``` -In words: the builder discards command output; a command failure leaves only the fixed parent code, while success may produce the existing final parent evidence. Netlify platform output outside the builder remains separate. +In words: the builder keeps fetched commit and file-tree values only for its internal check. It leaves all other helper output out of the build record. A helper failure leaves only the fixed parent code, while success may produce the existing final parent evidence. Netlify platform output outside the builder remains separate. 1. Ask the platform maintainer for the exact issues, pull requests, and commits. 2. Confirm the failure tests used only made-up values. -3. Confirm the child-output tests used local fake commands and no network. -4. Confirm the fake generic command covered both exit failure and signal termination. -5. Confirm the two fake Git reads covered `rev-parse` and `show` failures. -6. Confirm each synthetic failure stopped before release or provider work. +3. Confirm the helper-output tests used only made-up local helpers and no network. +4. Confirm the shared helper check covered a helper that ended with an error and one that was stopped. +5. Confirm the fetched-commit and file-tree checks each covered a failure. +6. Confirm each synthetic failure stopped before any network, publication, or outside-provider action. 7. Read the redacted test summary. -8. Confirm every failed wrapper's complete stderr line is `netlify_release_build_failed`. -9. Confirm every failed wrapper had empty stdout and an unsuccessful status. -10. Confirm no child output, thrown message, stack, path, URL, token-shaped canary, or trap canary appeared. -11. Confirm source contains no inherited child-output setting. +8. Confirm every failed wrapper's complete error-output line is `netlify_release_build_failed`. +9. Confirm every failed wrapper had no normal output and an unsuccessful status. +10. Confirm no helper output, failure message, detailed error trail, file location, website address, secret-shaped made-up value, or made-up inspection-warning value appeared. +11. Confirm reviewed source has no connection that copies helper output into the build record. 12. Confirm the full artifact-safety and release-workflow checks passed. 13. Confirm review found exact Git comparison, cleanup, artifacts, and the successful parent evidence line unchanged. 14. Record source, tests, merge, Netlify publication, website publication, `runmprc.com` verification, Firebase, provider, production data, and live behavior as separate states. -**Expected result:** the outer wrapper does not read or echo its caught value. Child commands write no bytes to the wrapper's output. Required Git commit and tree values remain internal. A failure leaves only the fixed parent code. The code means only that preparation failed. This check does not cover Netlify platform output outside the builder or prove that the complete provider log is safe. +**Expected result:** the outer wrapper does not read or echo its caught value. Helper programs add no normal or error output to the build record. Required fetched-commit and file-tree values remain internal. A failure leaves only the fixed parent code. The code means only that preparation failed. This check does not cover Netlify platform output outside the builder or prove that the complete provider log is safe. -**Stop conditions:** stop if anyone asks for a real credential, private provider value, production run, or raw log. Stop if a failed wrapper has stdout, more than the fixed stderr code, or a successful status. Stop if output contains child detail, a thrown value, stack, path, URL, token-shaped value, or private data. Stop if anyone calls the whole provider log sanitized or treats the fixed code as publication proof. +**Stop conditions:** stop if anyone asks for a real credential, private provider value, production run, or raw log. Stop if a failed wrapper has normal output, more than the fixed error-output code, or a successful status. Stop if output contains helper detail, a thrown value, stack, path, URL, token-shaped value, or private data. Stop if anyone calls the whole provider log sanitized or treats the fixed code as publication proof. -**Success proof:** keep the old-source failure summaries, green synthetic wrapper and fake-command results, the no-inherited-output check, complete artifact-safety and release-workflow results, reviewed commits, and separate statements for every delivery state. State plainly that Netlify, the website, `runmprc.com`, Firebase, outside providers, production data, and live behavior were not changed or verified unless each has separate dated proof. +**Success proof:** keep the old-source failure summaries, green synthetic wrapper and made-up-helper results, the no-helper-output-connection check, complete artifact-safety and release-workflow results, reviewed commits, and separate statements for every delivery state. State plainly that Netlify, the website, `runmprc.com`, Firebase, outside providers, production data, and live behavior were not changed or verified unless each has separate dated proof. -**Undo:** use one reviewed revert or safe roll-forward pull request. Never restore raw caught-error logging or inherited child output. Never rerun production only to discover the cause. +**Undo:** use one reviewed revert or safe roll-forward pull request. Never restore raw caught-error logging or copy helper output into the build record. Never rerun production only to discover the cause. **Escalation:** contact the platform and security owners. If an earlier log may contain a sensitive value, use the private incident path. Do not copy the value into GitHub, a screenshot, email, or an AI tool.