From 551dc135adc52bbafc827f14258413d9e2c0aa04 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:28:30 +0900 Subject: [PATCH 1/8] test(security): require production governance token capability --- ...onment-governance-token-capability.test.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 test/production-environment-governance-token-capability.test.ts diff --git a/test/production-environment-governance-token-capability.test.ts b/test/production-environment-governance-token-capability.test.ts new file mode 100644 index 000000000..b4f052c2d --- /dev/null +++ b/test/production-environment-governance-token-capability.test.ts @@ -0,0 +1,71 @@ +import { + chmodSync, + mkdtempSync, + rmSync, + symlinkSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + readDelegatedGithubToken, +} from "../scripts/production-environment-governance-audit.mjs"; + +const temporaryDirectories: string[] = []; + +function temporaryDirectory() { + const directory = mkdtempSync(join(tmpdir(), "noema-production-governance-token-")); + temporaryDirectories.push(directory); + return directory; +} + +function tokenFile(mode = 0o600) { + const directory = temporaryDirectory(); + const path = join(directory, "maintainer-app-token"); + writeFileSync(path, "short-lived-maintainer-token", { encoding: "utf8", mode }); + chmodSync(path, mode); + return path; +} + +afterEach(() => { + for (const directory of temporaryDirectories.splice(0)) { + rmSync(directory, { recursive: true, force: true }); + } +}); + +describe("production environment governance GitHub credential ingress", () => { + it("reads only an owner-only delegated capability file", () => { + expect(readDelegatedGithubToken(tokenFile())).toBe("short-lived-maintainer-token"); + + expect(() => readDelegatedGithubToken(tokenFile(0o640))).toThrow( + "Maintainer token file permissions must be owner-only.", + ); + }); + + it("refuses a symlinked delegated token capability", () => { + const directory = temporaryDirectory(); + const target = join(directory, "real-token"); + const link = join(directory, "token-link"); + writeFileSync(target, "short-lived-maintainer-token", { encoding: "utf8", mode: 0o600 }); + symlinkSync(target, link); + + expect(() => readDelegatedGithubToken(link)).toThrow( + "Maintainer token file could not be opened safely:", + ); + }); + + it("bootstraps the deployment audit through the token capability instead of GH_TOKEN", () => { + const workflow = require("node:fs").readFileSync(".github/workflows/cd.yml", "utf8"); + const auditStart = workflow.indexOf("- name: Audit production environment deployment protections"); + const auditEnd = workflow.indexOf("- name: Production evidence preflight", auditStart); + const auditStep = workflow.slice(auditStart, auditEnd); + + expect(auditStart).toBeGreaterThan(-1); + expect(auditEnd).toBeGreaterThan(auditStart); + expect(auditStep).toContain("NOEMA_MAINTAINER_TOKEN_PATH"); + expect(auditStep).toContain("umask 077"); + expect(auditStep).toContain("chmod 0600"); + expect(auditStep).not.toContain("GH_TOKEN: ${{ github.token }}"); + }); +}); From c212560029a8e23fae5f67e237bacb6576cb847c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:29:56 +0900 Subject: [PATCH 2/8] test(security): keep production token regression ESM-safe --- .../production-environment-governance-token-capability.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/production-environment-governance-token-capability.test.ts b/test/production-environment-governance-token-capability.test.ts index b4f052c2d..e1dfbc812 100644 --- a/test/production-environment-governance-token-capability.test.ts +++ b/test/production-environment-governance-token-capability.test.ts @@ -1,6 +1,7 @@ import { chmodSync, mkdtempSync, + readFileSync, rmSync, symlinkSync, writeFileSync, @@ -56,7 +57,7 @@ describe("production environment governance GitHub credential ingress", () => { }); it("bootstraps the deployment audit through the token capability instead of GH_TOKEN", () => { - const workflow = require("node:fs").readFileSync(".github/workflows/cd.yml", "utf8"); + const workflow = readFileSync(".github/workflows/cd.yml", "utf8"); const auditStart = workflow.indexOf("- name: Audit production environment deployment protections"); const auditEnd = workflow.indexOf("- name: Production evidence preflight", auditStart); const auditStep = workflow.slice(auditStart, auditEnd); From 233785735a554323bcf0e219e88a525bae9d3106 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:30:53 +0900 Subject: [PATCH 3/8] fix(security): read production governance token capability --- ...roduction-environment-governance-audit.mjs | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/scripts/production-environment-governance-audit.mjs b/scripts/production-environment-governance-audit.mjs index 66777a83c..30567fac3 100644 --- a/scripts/production-environment-governance-audit.mjs +++ b/scripts/production-environment-governance-audit.mjs @@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process"; import { appendFileSync, mkdirSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { pathToFileURL } from "node:url"; +import { readDelegatedGithubToken as readHardenedDelegatedGithubToken } from "./lib/delegated-github-token.mjs"; import { evaluateProductionEnvironment } from "./lib/production-environment-governance.mjs"; import { hasDuplicateJsonObjectKeys } from "./normalize-commercial-readiness-evidence.mjs"; @@ -36,10 +37,21 @@ export function redactSensitiveValue(value, sensitiveValues = []) { return redacted; } +/** + * Read a short-lived GitHub credential from the repository's hardened, + * descriptor-safe capability-file boundary. + * + * @param {unknown} tokenPath Explicit non-secret capability-file path. + * @returns {string} Exact delegated GitHub token bytes decoded as UTF-8. + */ +export function readDelegatedGithubToken(tokenPath) { + return readHardenedDelegatedGithubToken(tokenPath); +} + /** * Build the least-authority environment passed to the read-only GitHub CLI. * - * @param {NodeJS.ProcessEnv} [sourceEnvironment=process.env] Ambient process environment. + * @param {NodeJS.ProcessEnv} [sourceEnvironment=process.env] Explicit credential/config source. * @returns {Record} Allow-listed child-process environment. */ export function createGhSubprocessEnvironment(sourceEnvironment = process.env) { @@ -74,9 +86,9 @@ export function decodeGhOutput(value, label = "output") { /** * Execute one bounded GitHub CLI request for production-governance evidence. - * Runtime callers use the real shell-free spawn implementation and current - * process environment. Tests may inject only these two boundaries so failure - * byte selection, UTF-8 handling, and secret redaction are exercised directly. + * Runtime callers pass only an explicitly read delegated credential; tests may + * inject the subprocess primitive and an explicit environment to exercise the + * error/redaction boundary without granting network or ambient secret access. * * @param {string[]} args GitHub CLI arguments. * @param {{sourceEnvironment?: NodeJS.ProcessEnv, spawnSyncImpl?: typeof spawnSync}} [options] @@ -115,15 +127,24 @@ export function runGh( return decodeGhOutput(completed.stdout, "stdout").trim(); } -function collectEnvironment(repository, runGhImpl) { - const raw = runGhImpl([ +function collectEnvironment(repository, runGhImpl, delegatedGithubToken, sourceEnvironment) { + const args = [ "api", "-H", "Accept: application/vnd.github+json", "-H", "X-GitHub-Api-Version: 2026-03-10", `repos/${repository}/environments/production`, - ]); + ]; + const runOptions = delegatedGithubToken + ? { + sourceEnvironment: { + PATH: sourceEnvironment.PATH, + GH_TOKEN: delegatedGithubToken, + }, + } + : undefined; + const raw = runGhImpl(args, runOptions); if (!raw) { throw new Error("GitHub CLI returned an empty production environment response."); } @@ -203,9 +224,9 @@ function buildFailureReport(repository, error) { /** * Collect and evaluate the live production-environment governance evidence. - * Dependency injection is deliberately limited to the read-only GitHub CLI, - * process environment, logging sink, and exit-code sink so realistic tests can - * exercise every evidence boundary without granting network or write authority. + * The production entrypoint requires a descriptor-safe delegated credential. + * Dependency-injected GitHub clients remain credential-free test seams so + * malformed/hostile evidence can be exercised without network authority. * * @param {{ * sourceEnvironment?: NodeJS.ProcessEnv, @@ -229,12 +250,21 @@ export function main( const reportPath = String( sourceEnvironment.NOEMA_PRODUCTION_ENVIRONMENT_GOVERNANCE_PATH ?? defaultReportPath, ).trim() || defaultReportPath; + const tokenPath = String(sourceEnvironment.NOEMA_MAINTAINER_TOKEN_PATH ?? "").trim(); let report; try { if (!repositoryPattern.test(repository)) { throw new Error("GITHUB_REPOSITORY must identify a ContextualWisdomLab repository."); } - const environment = collectEnvironment(repository, runGhImpl); + const delegatedGithubToken = runGhImpl === runGh + ? readDelegatedGithubToken(tokenPath) + : null; + const environment = collectEnvironment( + repository, + runGhImpl, + delegatedGithubToken, + sourceEnvironment, + ); const evaluation = evaluateProductionEnvironment(environment); report = { schema_version: 1, From cc558a980879264c4f84a976c2fb0e7502ede2d6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:31:40 +0900 Subject: [PATCH 4/8] fix(security): broker production governance token via capability file --- .github/workflows/cd.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1d3ba4584..9c91bdd4b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -117,9 +117,19 @@ jobs: - name: Audit production environment deployment protections env: - GH_TOKEN: ${{ github.token }} + DELEGATED_MAINTAINER_TOKEN: ${{ github.token }} NOEMA_PRODUCTION_ENVIRONMENT_GOVERNANCE_PATH: artifacts/governance/production-environment-governance.json - run: npm run production:governance + run: | + set -euo pipefail + token_dir="$RUNNER_TEMP/noema-production-governance" + token_path="$token_dir/maintainer-app-token" + mkdir -p "$token_dir" + umask 077 + printf '%s' "$DELEGATED_MAINTAINER_TOKEN" > "$token_path" + chmod 0600 "$token_path" + unset DELEGATED_MAINTAINER_TOKEN + trap 'rm -f "$token_path"' EXIT + NOEMA_MAINTAINER_TOKEN_PATH="$token_path" npm run production:governance - name: Production evidence preflight env: From 361ba19fe3865a3b062d9d44540b1cb7c2987b10 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:33:13 +0900 Subject: [PATCH 5/8] test(security): reject ambient production governance token --- ...onment-governance-token-capability.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/production-environment-governance-token-capability.test.ts b/test/production-environment-governance-token-capability.test.ts index e1dfbc812..87df35cd3 100644 --- a/test/production-environment-governance-token-capability.test.ts +++ b/test/production-environment-governance-token-capability.test.ts @@ -10,6 +10,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { + main, readDelegatedGithubToken, } from "../scripts/production-environment-governance-audit.mjs"; @@ -56,6 +57,28 @@ describe("production environment governance GitHub credential ingress", () => { ); }); + it("fails closed instead of falling back to an ambient GH_TOKEN", () => { + const directory = temporaryDirectory(); + const report = main({ + sourceEnvironment: { + GITHUB_REPOSITORY: "ContextualWisdomLab/noema", + GH_TOKEN: "ambient-token-must-not-be-used", + NOEMA_PRODUCTION_ENVIRONMENT_GOVERNANCE_PATH: join(directory, "report.json"), + }, + log: () => undefined, + setExitCode: () => undefined, + }); + + expect(report).toMatchObject({ status: "FAIL" }); + expect(report.failures[0]).toMatchObject({ + code: "production_environment_collection_failed", + detail: "Maintainer token file path is required.", + }); + expect(readFileSync(join(directory, "report.json"), "utf8")).not.toContain( + "ambient-token-must-not-be-used", + ); + }); + it("bootstraps the deployment audit through the token capability instead of GH_TOKEN", () => { const workflow = readFileSync(".github/workflows/cd.yml", "utf8"); const auditStart = workflow.indexOf("- name: Audit production environment deployment protections"); From 16bf12b4f2b9262bc5acfb813b7727834a808746 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:36:47 +0900 Subject: [PATCH 6/8] refactor(testability): keep production token options branchless --- .../production-environment-governance-audit.mjs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/scripts/production-environment-governance-audit.mjs b/scripts/production-environment-governance-audit.mjs index 30567fac3..d4ef17971 100644 --- a/scripts/production-environment-governance-audit.mjs +++ b/scripts/production-environment-governance-audit.mjs @@ -136,15 +136,12 @@ function collectEnvironment(repository, runGhImpl, delegatedGithubToken, sourceE "X-GitHub-Api-Version: 2026-03-10", `repos/${repository}/environments/production`, ]; - const runOptions = delegatedGithubToken - ? { - sourceEnvironment: { - PATH: sourceEnvironment.PATH, - GH_TOKEN: delegatedGithubToken, - }, - } - : undefined; - const raw = runGhImpl(args, runOptions); + const raw = runGhImpl(args, { + sourceEnvironment: { + PATH: sourceEnvironment.PATH, + GH_TOKEN: delegatedGithubToken, + }, + }); if (!raw) { throw new Error("GitHub CLI returned an empty production environment response."); } From 92cbb093b141a97c92e8c0331712aeafee237e7a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:57:47 +0900 Subject: [PATCH 7/8] test(security): reject fixed token bootstrap directory --- ...onment-governance-token-capability.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/production-environment-governance-token-capability.test.ts b/test/production-environment-governance-token-capability.test.ts index 87df35cd3..bcfc361a2 100644 --- a/test/production-environment-governance-token-capability.test.ts +++ b/test/production-environment-governance-token-capability.test.ts @@ -92,4 +92,23 @@ describe("production environment governance GitHub credential ingress", () => { expect(auditStep).toContain("chmod 0600"); expect(auditStep).not.toContain("GH_TOKEN: ${{ github.token }}"); }); + + it("creates a fresh private directory before writing delegated token bytes", () => { + const workflow = readFileSync(".github/workflows/cd.yml", "utf8"); + const auditStart = workflow.indexOf("- name: Audit production environment deployment protections"); + const auditEnd = workflow.indexOf("- name: Production evidence preflight", auditStart); + const auditStep = workflow.slice(auditStart, auditEnd); + const umaskIndex = auditStep.indexOf("umask 077"); + const mktempIndex = auditStep.indexOf( + 'token_dir="$(mktemp -d "$RUNNER_TEMP/noema-production-governance.XXXXXX")"', + ); + + expect(auditStart).toBeGreaterThan(-1); + expect(auditEnd).toBeGreaterThan(auditStart); + expect(umaskIndex).toBeGreaterThan(-1); + expect(mktempIndex).toBeGreaterThan(umaskIndex); + expect(auditStep).not.toContain('token_dir="$RUNNER_TEMP/noema-production-governance"'); + expect(auditStep).not.toContain('mkdir -p "$token_dir"'); + expect(auditStep).toContain("trap 'rm -rf \"$token_dir\"' EXIT"); + }); }); From 88b2e9a4757aec9b26faac414e3ffa1a359290ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:59:11 +0900 Subject: [PATCH 8/8] fix(security): isolate delegated token bootstrap directory --- .github/workflows/cd.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9c91bdd4b..0427c92ef 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -121,14 +121,13 @@ jobs: NOEMA_PRODUCTION_ENVIRONMENT_GOVERNANCE_PATH: artifacts/governance/production-environment-governance.json run: | set -euo pipefail - token_dir="$RUNNER_TEMP/noema-production-governance" - token_path="$token_dir/maintainer-app-token" - mkdir -p "$token_dir" umask 077 + token_dir="$(mktemp -d "$RUNNER_TEMP/noema-production-governance.XXXXXX")" + token_path="$token_dir/maintainer-app-token" + trap 'rm -rf "$token_dir"' EXIT printf '%s' "$DELEGATED_MAINTAINER_TOKEN" > "$token_path" chmod 0600 "$token_path" unset DELEGATED_MAINTAINER_TOKEN - trap 'rm -f "$token_path"' EXIT NOEMA_MAINTAINER_TOKEN_PATH="$token_path" npm run production:governance - name: Production evidence preflight @@ -260,7 +259,7 @@ jobs: repository: $repository, releaseTag: $releaseTag, commitSha: $commitSha, - deploymentEvidenceSha256: $deploymentEvidenceSha256, + deploymentEvidenceSha256: $deployment_digest, signerWorkflow: $signerWorkflow, predicateType: $predicateType, oidcIssuer: $oidcIssuer,