Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions .github/workflows/hourly-commercial-readiness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,16 +163,34 @@ jobs:
- name: verify active main governance before any write
id: governance
env:
GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}
DELEGATED_MAINTAINER_TOKEN: ${{ steps.maintainer_app.outputs.token }}
NOEMA_GOVERNANCE_AUDIT_PATH: artifacts/governance/main-governance-audit.json
run: npm run governance:audit
run: |
set -euo pipefail
token_dir="$RUNNER_TEMP/noema-hourly-commercial-readiness"
token_path="$token_dir/maintainer-app-token"
mkdir -p "$token_dir"
umask 077
printf '%s' "$DELEGATED_MAINTAINER_TOKEN" > "$token_path"
unset DELEGATED_MAINTAINER_TOKEN
trap 'rm -f "$token_path"' EXIT
NOEMA_MAINTAINER_TOKEN_PATH="$token_path" npm run governance:audit

- name: inspect, dispatch, and merge exact-head pull requests
id: loop
env:
GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}
DELEGATED_MAINTAINER_TOKEN: ${{ steps.maintainer_app.outputs.token }}
NOEMA_REVIEWER_LOGIN: ${{ vars.NOEMA_REVIEWER_LOGIN }}
run: node scripts/hourly-commercial-readiness.mjs --apply
run: |
set -euo pipefail
token_dir="$RUNNER_TEMP/noema-hourly-commercial-readiness"
token_path="$token_dir/commercial-loop-token"
mkdir -p "$token_dir"
umask 077
printf '%s' "$DELEGATED_MAINTAINER_TOKEN" > "$token_path"
unset DELEGATED_MAINTAINER_TOKEN
trap 'rm -f "$token_path"' EXIT
NOEMA_MAINTAINER_TOKEN_PATH="$token_path" node scripts/hourly-commercial-readiness.mjs --apply

- name: refresh saleable-readiness evidence when the queue is empty
if: steps.loop.outputs.remaining_open_pull_request_count == '0'
Expand Down
25 changes: 21 additions & 4 deletions .github/workflows/maintainer-app-readiness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,18 @@ jobs:
id: governance
continue-on-error: true
env:
GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}
DELEGATED_MAINTAINER_TOKEN: ${{ steps.maintainer_app.outputs.token }}
NOEMA_GOVERNANCE_AUDIT_PATH: ${{ runner.temp }}/noema-maintainer-app-readiness/main-governance-audit.json
run: node scripts/main-governance-audit.mjs
run: |
set -euo pipefail
token_dir="$RUNNER_TEMP/noema-maintainer-app-readiness"
token_path="$token_dir/main-governance-token"
mkdir -p "$token_dir"
umask 077
printf '%s' "$DELEGATED_MAINTAINER_TOKEN" > "$token_path"
unset DELEGATED_MAINTAINER_TOKEN
trap 'rm -f "$token_path"' EXIT
NOEMA_MAINTAINER_TOKEN_PATH="$token_path" node scripts/main-governance-audit.mjs

- name: audit effective Maintainer App identity and access
id: readiness
Expand Down Expand Up @@ -109,7 +118,7 @@ jobs:
if: always()
continue-on-error: true
env:
GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}
DELEGATED_MAINTAINER_TOKEN: ${{ steps.maintainer_app.outputs.token }}
NOEMA_REVIEWER_LOGIN: ${{ vars.NOEMA_REVIEWER_LOGIN }}
MAINTAINER_APP_OUTCOME: ${{ steps.maintainer_app.outcome }}
run: |
Expand Down Expand Up @@ -153,8 +162,16 @@ jobs:
exit 1
fi

token_dir="$RUNNER_TEMP/noema-maintainer-app-readiness"
token_path="$token_dir/commercial-loop-token"
mkdir -p "$token_dir"
umask 077
printf '%s' "$DELEGATED_MAINTAINER_TOKEN" > "$token_path"
unset DELEGATED_MAINTAINER_TOKEN
trap 'rm -f "$token_path"' EXIT

set +e
node scripts/hourly-commercial-readiness.mjs --report "$report_path"
NOEMA_MAINTAINER_TOKEN_PATH="$token_path" node scripts/hourly-commercial-readiness.mjs --report "$report_path"
loop_status=$?
set -e
if [ "$loop_status" -ne 0 ] && [ ! -s "$report_path" ]; then
Expand Down
3 changes: 2 additions & 1 deletion scripts/hourly-commercial-readiness.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { evaluatePullRequest } from "./lib/commercial-readiness-loop.mjs";
import { readDelegatedGithubToken } from "./lib/delegated-github-token.mjs";

const MAX_ERROR_CHARS = 4_000;
const MAX_REPORT_DETAIL_CHARS = 1_000;
Expand Down Expand Up @@ -57,7 +58,7 @@ export function createGhSubprocessEnvironment(sourceEnvironment) {
function runGh(args, { input } = {}) {
const childEnvironment = createGhSubprocessEnvironment({
PATH: process.env.PATH,
GH_TOKEN: process.env.GH_TOKEN,
GH_TOKEN: readDelegatedGithubToken(process.env.NOEMA_MAINTAINER_TOKEN_PATH),
});
const completed = spawnSync("gh", args, {
encoding: "utf8",
Expand Down
31 changes: 31 additions & 0 deletions scripts/lib/delegated-github-token.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { readFileSync } from "node:fs";

/**
* Load a short-lived delegated GitHub token from an explicit capability file.
*
* The file path is non-secret runtime configuration. The bearer token itself
* must not be read from the Node process environment. Callers are responsible
* for creating the file with restrictive permissions in trusted bootstrap code
* and deleting it after use.
*/
export function readDelegatedGithubToken(tokenPath) {
const path = String(tokenPath ?? "").trim();
if (!path) {
throw new Error("Maintainer token file path is required.");
}

let token;
try {
token = readFileSync(path, "utf8");
} catch (error) {
throw new Error(`Maintainer token file could not be read: ${String(error?.message ?? error)}`);
}

if (!token) {
throw new Error("Maintainer token file must not be empty.");
}
if (/[\u0000-\u001f\u007f]/.test(token)) {
throw new Error("Maintainer token must not contain control characters.");
}
return token;
}
21 changes: 12 additions & 9 deletions scripts/main-governance-audit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from "./lib/delegated-github-token.mjs";
import { evaluateMainGovernanceRules } from "./lib/main-governance-audit.mjs";

const MAX_ERROR_CHARS = 4_000;
Expand Down Expand Up @@ -35,7 +36,7 @@ export function redactSensitiveValue(value, sensitiveValues = []) {
return redacted;
}

export function createGhSubprocessEnvironment(sourceEnvironment = process.env) {
export function createGhSubprocessEnvironment(sourceEnvironment = {}) {
const childEnvironment = {
GH_HOST: "github.com",
NO_COLOR: "1",
Expand All @@ -49,8 +50,11 @@ export function createGhSubprocessEnvironment(sourceEnvironment = process.env) {
return childEnvironment;
}

function runGh(args) {
const childEnvironment = createGhSubprocessEnvironment();
function runGh(args, delegatedGithubToken) {
const childEnvironment = createGhSubprocessEnvironment({
PATH: process.env.PATH,
GH_TOKEN: delegatedGithubToken,
});
const completed = spawnSync("gh", ["api", ...githubApiHeaders, ...args], {
encoding: "utf8",
maxBuffer: MAX_GH_OUTPUT_BYTES,
Expand All @@ -70,8 +74,8 @@ function runGh(args) {
return completed.stdout.trim();
}

function runGhJson(args) {
const raw = runGh(args);
function runGhJson(args, delegatedGithubToken) {
const raw = runGh(args, delegatedGithubToken);
if (!raw) {
throw new Error("GitHub CLI returned an empty active-rules response.");
}
Expand Down Expand Up @@ -177,16 +181,15 @@ export function main() {
const repository = String(process.env.GITHUB_REPOSITORY ?? "").trim();
const reportPath = String(process.env.NOEMA_GOVERNANCE_AUDIT_PATH ?? defaultReportPath).trim()
|| defaultReportPath;
const tokenPath = String(process.env.NOEMA_MAINTAINER_TOKEN_PATH ?? "").trim();
let report;
try {
if (!repositoryPattern.test(repository)) {
throw new Error("GITHUB_REPOSITORY must identify a ContextualWisdomLab repository.");
}
if (!process.env.GH_TOKEN) {
throw new Error("GH_TOKEN is required for the governance audit.");
}
const delegatedGithubToken = readDelegatedGithubToken(tokenPath);
const endpoint = `repos/${repository}/rules/branches/main?per_page=100`;
const pages = runGhJson(["--paginate", "--slurp", endpoint]);
const pages = runGhJson(["--paginate", "--slurp", endpoint], delegatedGithubToken);
const rules = flattenRulePages(pages);
report = buildReport(repository, rules, evaluateMainGovernanceRules(rules));
} catch (error) {
Expand Down
87 changes: 87 additions & 0 deletions test/github-credential-capability-ingress.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { mkdtempSync, readFileSync, rmSync, 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/lib/delegated-github-token.mjs";

const temporaryDirectories: string[] = [];

function temporaryFile(contents: string) {
const directory = mkdtempSync(join(tmpdir(), "noema-token-capability-"));
temporaryDirectories.push(directory);
const path = join(directory, "token");
writeFileSync(path, contents, { encoding: "utf8", mode: 0o600 });
return path;
}

function stepBlock(workflow: string, name: string) {
const start = workflow.indexOf(name);
const nextStep = workflow.indexOf("\n - name:", start + 1);
expect(start).toBeGreaterThanOrEqual(0);
expect(nextStep).toBeGreaterThan(start);
return workflow.slice(start, nextStep);
}

afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
rmSync(directory, { recursive: true, force: true });
}
});

describe("GitHub credential capability ingress", () => {
it("reads a non-empty control-free delegated token from the explicit capability path", () => {
const path = temporaryFile("delegated-token-value");
expect(readDelegatedGithubToken(path)).toBe("delegated-token-value");
});

it("fails closed for missing, unreadable, empty, and control-bearing capability files", () => {
expect(() => readDelegatedGithubToken("")).toThrow("Maintainer token file path is required.");
expect(() => readDelegatedGithubToken("/definitely/not/a/noema/token")).toThrow(
"Maintainer token file could not be read:",
);
expect(() => readDelegatedGithubToken(temporaryFile(""))).toThrow(
"Maintainer token file must not be empty.",
);
expect(() => readDelegatedGithubToken(temporaryFile("token\nvalue"))).toThrow(
"Maintainer token must not contain control characters.",
);
});

it("keeps delegated GitHub bearer tokens out of Node process-environment reads", () => {
for (const scriptPath of [
"scripts/main-governance-audit.mjs",
"scripts/hourly-commercial-readiness.mjs",
]) {
const script = readFileSync(scriptPath, "utf8");
expect(script).toContain("NOEMA_MAINTAINER_TOKEN_PATH");
expect(script).toContain("readDelegatedGithubToken");
expect(script).not.toContain("process.env.GH_TOKEN");
}
});

it("bootstraps governance and commercial-loop callers through restrictive ephemeral capability files", () => {
const workflowCases = [
{
path: ".github/workflows/hourly-commercial-readiness.yml",
steps: ["verify active main governance before any write", "inspect, dispatch, and merge exact-head pull requests"],
},
{
path: ".github/workflows/maintainer-app-readiness.yml",
steps: ["audit active main governance", "inspect commercial-readiness loop without writes"],
},
];

for (const workflowCase of workflowCases) {
const workflow = readFileSync(workflowCase.path, "utf8");
for (const stepName of workflowCase.steps) {
const block = stepBlock(workflow, stepName);
expect(block).toContain("DELEGATED_MAINTAINER_TOKEN: ${{ steps.maintainer_app.outputs.token }}");
expect(block).toContain("NOEMA_MAINTAINER_TOKEN_PATH");
expect(block).toContain("umask 077");
expect(block).toContain("unset DELEGATED_MAINTAINER_TOKEN");
expect(block).toContain("trap 'rm -f \"$token_path\"' EXIT");
expect(block).not.toContain("GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}");
}
}
});
});
10 changes: 9 additions & 1 deletion test/hourly-commercial-readiness-toolchain-integrity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,21 @@ describe("commercial writer toolchain integrity", () => {

const tokenConsumers = workflowSteps()
.filter((step) =>
step.block.includes("GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}"),
step.block.includes("DELEGATED_MAINTAINER_TOKEN: ${{ steps.maintainer_app.outputs.token }}"),
)
.map((step) => step.name);
expect(tokenConsumers).toEqual([
"verify active main governance before any write",
"inspect, dispatch, and merge exact-head pull requests",
]);
for (const stepName of tokenConsumers) {
const block = uniqueStep(stepName).block;
expect(block).toContain("NOEMA_MAINTAINER_TOKEN_PATH");
expect(block).toContain("umask 077");
expect(block).toContain("unset DELEGATED_MAINTAINER_TOKEN");
expect(block).toContain("trap 'rm -f \"$token_path\"' EXIT");
}
expect(workflow).not.toContain("GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}");
expect(workflow).not.toContain("GH_TOKEN: ${{ github.token }}");
});

Expand Down
6 changes: 4 additions & 2 deletions test/main-governance-audit-script.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ describe("main governance audit GitHub adapter", () => {
expect(script).toContain('["--paginate", "--slurp", endpoint]');
expect(script).toContain("rules/branches/main?per_page=100");
expect(script).toContain("evaluateMainGovernanceRules");
expect(script).toContain("NOEMA_MAINTAINER_TOKEN_PATH");
expect(script).not.toContain("process.env.GH_TOKEN");
});

it("writes single-line bounded evidence, outputs, and a workflow summary without leaking the token", () => {
Expand All @@ -100,10 +102,10 @@ describe("main governance audit GitHub adapter", () => {
expect(script).not.toContain("JSON.stringify(process.env");
});

it("fails closed when credentials, the audit, or collection do not pass", () => {
it("fails closed when the capability, audit, or collection do not pass", () => {
const script = readFileSync("scripts/main-governance-audit.mjs", "utf8");

expect(script).toContain("GH_TOKEN is required for the governance audit.");
expect(script).toContain("readDelegatedGithubToken(tokenPath)");
expect(script).toContain('if (report.status !== "PASS")');
expect(script).toContain("process.exitCode = 1");
expect(script).toContain('status: "FAIL"');
Expand Down
4 changes: 3 additions & 1 deletion test/workflow-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ describe("deployment workflow readiness gates", () => {
]) {
expect(workflow).toContain(permission);
}
expect(workflow).toContain("GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}");
expect(workflow).toContain("DELEGATED_MAINTAINER_TOKEN: ${{ steps.maintainer_app.outputs.token }}");
expect(workflow).toContain("NOEMA_MAINTAINER_TOKEN_PATH");
expect(workflow).not.toContain("GH_TOKEN: ${{ steps.maintainer_app.outputs.token }}");
expect(workflow).not.toContain("GH_TOKEN: ${{ github.token }}");
expect(workflow).toContain("permissions:\n contents: read");
expect(workflow).not.toContain("id-token: write");
Expand Down
Loading