From 7bc0c77d295f66463ef76cc3f07169fefcaf5e6c Mon Sep 17 00:00:00 2001 From: Pavithran R A <92867708+Pavithran-R-A@users.noreply.github.com> Date: Sun, 8 Nov 2026 17:31:58 +0530 Subject: [PATCH 1/4] test(security): reject job-wide GitHub token exposure in live validation --- .../live-validation-workflow-policy.test.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/security/live-validation-workflow-policy.test.ts b/tests/security/live-validation-workflow-policy.test.ts index c1fa84d..9e4d6f0 100644 --- a/tests/security/live-validation-workflow-policy.test.ts +++ b/tests/security/live-validation-workflow-policy.test.ts @@ -50,7 +50,43 @@ describe('the controlled live-validation workflow', () => { expect(WORKFLOW).not.toMatch(/NPM_TOKEN|NODE_AUTH_TOKEN|GITHUB_TOKEN:\s*\$\{\{\s*secrets/); }); - it('uses the ephemeral GitHub token only for MergeSutra-owned reads', () => { + it('never gives a GitHub token to job-wide install, build, test or audit commands', () => { + // Two token references are not enough: the previous policy passed even + // when both were at job scope, exposing them to npm ci and npm run check. + const scopes = [ + { + body: job('no-key-chain', 'live-model'), + allowedStep: 'Walk the credential-free fixture chain', + }, + { + body: job('live-model'), + allowedStep: 'Run the bounded real-model chain', + }, + ]; + + for (const { body, allowedStep } of scopes) { + const stepsStart = body.indexOf(' steps:\n'); + expect(stepsStart, 'the job has no steps boundary').toBeGreaterThanOrEqual(0); + const jobHeader = body.slice(0, stepsStart); + expect(jobHeader, 'GH_TOKEN must not be defined for an entire job').not.toMatch( + /^\s*GH_TOKEN:/m, + ); + + const sections = body.split(/^ - name: /m).slice(1); + const exposed = sections + .filter((section) => /^ {10}GH_TOKEN:/m.test(section)) + .map((section) => section.split('\n')[0]); + expect(exposed, 'only the trusted E2E driver may receive the token').toEqual([ + allowedStep, + ]); + + const driver = sections.find((section) => section.split('\n')[0] === allowedStep); + expect(driver, 'the driver step is missing').toBeDefined(); + expect(driver).toMatch( + /^ {8}env:\n {10}GH_TOKEN: \$\{\{ github\.token \}\}\n/m, + ); + } + expect(WORKFLOW.match(/GH_TOKEN:\s*\$\{\{ github\.token \}\}/g) ?? []).toHaveLength(2); expect(WORKFLOW).not.toMatch(/GITHUB_TOKEN:/); }); From bbce2ad1a0dd64b734799cb64fb7369e8e10397f Mon Sep 17 00:00:00 2001 From: Pavithran R A <92867708+Pavithran-R-A@users.noreply.github.com> Date: Sun, 8 Nov 2026 17:32:19 +0530 Subject: [PATCH 2/4] fix(security): scope GitHub token to trusted E2E driver steps --- .github/workflows/live-validation.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/live-validation.yml b/.github/workflows/live-validation.yml index c50e895..9cfecab 100644 --- a/.github/workflows/live-validation.yml +++ b/.github/workflows/live-validation.yml @@ -33,7 +33,6 @@ jobs: permissions: contents: read env: - GH_TOKEN: ${{ github.token }} NO_COLOR: '1' steps: - name: Checkout @@ -55,6 +54,10 @@ jobs: run: npm run build - name: Walk the credential-free fixture chain + # The trusted driver reads a public GitHub issue through gh. Repository + # commands run as separate children and strip GH_TOKEN before execution. + env: + GH_TOKEN: ${{ github.token }} run: node scripts/e2e-validation.mjs no-key - name: Upload sanitized no-key evidence @@ -78,7 +81,6 @@ jobs: permissions: contents: read env: - GH_TOKEN: ${{ github.token }} NO_COLOR: '1' # The image and engine the product's repository runner must use. The proof step # below preloads and measures this same digest; a repository command never runs @@ -204,7 +206,11 @@ jobs: run: npm run check - name: Run the bounded real-model chain + # Only this trusted driver can use gh for GitHub API reads. All install, + # build, audit, deterministic tests and sandbox proofs run token-free. + # Workspace-facing children are given a stripped environment by the driver. env: + GH_TOKEN: ${{ github.token }} BHARATCODE_API_KEY: ${{ secrets.BHARATCODE_API_KEY }} BHARATCODE_MODEL: ${{ inputs.model }} # Below the transport's own ceiling, not above it. Node's fetch gives up at From f5aa24bb9fcbb1b9ef140cb7f4661e3008232d17 Mon Sep 17 00:00:00 2001 From: Pavithran R A <92867708+Pavithran-R-A@users.noreply.github.com> Date: Sun, 8 Nov 2026 17:37:23 +0530 Subject: [PATCH 3/4] style: format live-workflow token-scope regression assertion --- tests/security/live-validation-workflow-policy.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/security/live-validation-workflow-policy.test.ts b/tests/security/live-validation-workflow-policy.test.ts index 9e4d6f0..023af6a 100644 --- a/tests/security/live-validation-workflow-policy.test.ts +++ b/tests/security/live-validation-workflow-policy.test.ts @@ -76,15 +76,11 @@ describe('the controlled live-validation workflow', () => { const exposed = sections .filter((section) => /^ {10}GH_TOKEN:/m.test(section)) .map((section) => section.split('\n')[0]); - expect(exposed, 'only the trusted E2E driver may receive the token').toEqual([ - allowedStep, - ]); + expect(exposed, 'only the trusted E2E driver may receive the token').toEqual([allowedStep]); const driver = sections.find((section) => section.split('\n')[0] === allowedStep); expect(driver, 'the driver step is missing').toBeDefined(); - expect(driver).toMatch( - /^ {8}env:\n {10}GH_TOKEN: \$\{\{ github\.token \}\}\n/m, - ); + expect(driver).toMatch(/^ {8}env:\n {10}GH_TOKEN: \$\{\{ github\.token \}\}\n/m); } expect(WORKFLOW.match(/GH_TOKEN:\s*\$\{\{ github\.token \}\}/g) ?? []).toHaveLength(2); From 55f1351ba5fdec254e6ce5b027f5c1919c89e835 Mon Sep 17 00:00:00 2001 From: Pavithran R A <92867708+Pavithran-R-A@users.noreply.github.com> Date: Sun, 8 Nov 2026 17:38:55 +0530 Subject: [PATCH 4/4] style(security): satisfy no-regex-spaces lint rule --- tests/security/live-validation-workflow-policy.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/security/live-validation-workflow-policy.test.ts b/tests/security/live-validation-workflow-policy.test.ts index 023af6a..3c8d288 100644 --- a/tests/security/live-validation-workflow-policy.test.ts +++ b/tests/security/live-validation-workflow-policy.test.ts @@ -72,7 +72,7 @@ describe('the controlled live-validation workflow', () => { /^\s*GH_TOKEN:/m, ); - const sections = body.split(/^ - name: /m).slice(1); + const sections = body.split(/^ {6}- name: /m).slice(1); const exposed = sections .filter((section) => /^ {10}GH_TOKEN:/m.test(section)) .map((section) => section.split('\n')[0]);