Skip to content

Redact MCP gateway bearers from diagnostic artifacts - #50961

Merged
pelikhan merged 7 commits into
mainfrom
copilot/fix-exposed-mcp-gateway-bearer
Aug 7, 2026
Merged

Redact MCP gateway bearers from diagnostic artifacts#50961
pelikhan merged 7 commits into
mainfrom
copilot/fix-exposed-mcp-gateway-bearer

Conversation

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

MCP gateway session bearers could persist in uploaded diagnostic logs. The bearer is runtime credential material and must not be retained in workflow artifacts.

  • Always run redaction

    • Execute the redaction script even when workflows declare no GitHub secrets.
  • Discover gateway credentials

    • Extract bearer values from gateway configurations across /tmp, runner temp, Copilot, Gemini, and Codex formats.
    • Support JSON headers and Codex TOML Authorization entries.
  • Scrub diagnostic logs

    • Redact raw bearer occurrences before artifact upload, including github.log, mcp-gateway.log, safeoutputs.log, and stderr.log.
const redacted = redactSecrets(logContent, gatewayTokens).content;
// session=<bearer> → session=***REDACTED***
  • Regression coverage
    • Verify redaction executes without declared workflow secrets.
    • Verify all diagnostic log streams and Codex configuration tokens are scrubbed.

Run context: https://github.com/github/gh-aw/actions/runs/31142787192> Generated by 👨‍🍳 PR Sous Chef · gpt54 · 11.8 AIC · ⊞ 8.3K ·

Comment /souschef to run again

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for tackling this security issue! 🔒 The PR is properly framed and addresses a critical credential-exposure vulnerability (MCP gateway bearer tokens appearing in diagnostic logs). Since this is a draft by the core team working through the implementation plan, you're on the right track.

Once you move out of draft, here are the key areas from the issue to keep in mind:

  1. Prevent raw bearer exposure — Ensure the authentication value never appears in logs or artifacts.
  2. Non-secret correlation — If you need to tie log streams together, use a truncated hash or separate correlation ID instead of the bearer itself.
  3. Scrub existing outputs — Include log sanitization before artifact upload to cover components outside gh-aw's control.
  4. Add regression tests — Prove the raw bearer cannot leak via unit or integration tests.

Looks good! Keep the PR description updated as you make progress.

Generated by ✅ Contribution Check · auto · 50.2 AIC · ⊞ 8.7K ·

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix exposure of MCP gateway agent/session bearer in diagnostic logs Redact MCP gateway bearers from diagnostic artifacts Aug 7, 2026
Copilot AI requested a review from pelikhan August 7, 2026 01:54
@pelikhan
pelikhan marked this pull request as ready for review August 7, 2026 02:19
Copilot AI balanced review requested due to automatic review settings August 7, 2026 02:19
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (33 additions across 4 files).

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

Warning

Threat Detection Engine Failure — The analysis engine could not complete. This is a tooling failure, not a security finding.

What happened

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Test Quality Sentinel. Review the logs for details.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Matt Pocock Skills Reviewer. Review the logs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ensures MCP gateway bearer credentials are scrubbed from diagnostic artifacts.

Changes:

  • Always invokes secret redaction.
  • Discovers bearer tokens across JSON and Codex TOML configurations.
  • Tests redaction across diagnostic logs and secret-free workflows.
Show a summary per file
File Description
pkg/workflow/redact_secrets.go Always generates the redaction action step.
pkg/workflow/redact_secrets_test.go Covers workflows without declared secrets.
actions/setup/js/redact_secrets.cjs Discovers gateway credentials from additional configurations.
actions/setup/js/redact_secrets.test.cjs Covers Codex tokens and diagnostic log streams.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread pkg/workflow/redact_secrets.go Outdated
Comment on lines +166 to +169
// Add environment variables
yaml.WriteString(" env:\n")

if len(secretReferences) > 0 {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8eea487 — the env: block is now emitted inside the len(secretReferences) > 0 guard, so the key is omitted entirely when a workflow declares no secrets. Hardened further in bfefcc2: the tests now parse the generated step as YAML and assert env is absent for secret-free workflows and is a populated mapping when secrets exist, so a null env is caught structurally rather than by substring matching.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Redact MCP gateway bearers from diagnostic artifacts

This PR correctly addresses the MCP bearer token leak in diagnostic artifacts. The approach — always running the redaction step, extracting tokens from multiple config formats (JSON + TOML), and covering all relevant log files — is sound.

One blocking bug found:

Empty env: block → invalid YAML

In pkg/workflow/redact_secrets.go, the refactor always emits:

        env:

...but only conditionally writes child entries (when secretReferences is non-empty). A bare env: key with no mappings is invalid YAML and will cause GitHub Actions to reject the generated workflow file for all workflows that declare no secrets.

The env: header write must be guarded by the same if len(secretReferences) > 0 condition that wraps its content (or moved inside that block).

The new test TestSecretRedactionRunsWithoutWorkflowSecrets doesn't catch this because it doesn't validate YAML structure.

Other observations (non-blocking)
  • TOML regex scope (redact_secrets.cjs line 121): /Authorization\s*=\s*"([^"]+)"/g is intentionally broad to cover Codex TOML headers — this is acceptable given the security goal, but worth documenting.
  • Test coverage for empty env block in Go: add an assertion in TestSecretRedactionRunsWithoutWorkflowSecrets that the output does not contain a bare env: with no following indented key.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 28.8 AIC · ⊞ 5.3K

Comment thread pkg/workflow/redact_secrets.go Outdated
yaml.WriteString(" await main();\n")

// Add environment variables
yaml.WriteString(" env:\n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: empty env: block generates invalid YAML when no secrets are declared

The env: key is always written (this line), but when secretReferences is empty the block has no child entries. GitHub Actions rejects a step with a bare env: key and no mappings — this will break all compiled workflows where no workflow secrets are declared.

Suggested fix — guard the env: header inside the secrets block:

if len(secretReferences) > 0 {
    yaml.WriteString("        env:\n")
    // ... existing env entries ...
}

The new test TestSecretRedactionRunsWithoutWorkflowSecrets doesn't catch this because it only asserts the presence of the main() call, not that the generated YAML is structurally valid.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8eea487 — the env: header is now written inside the len(secretReferences) > 0 guard, and TestSecretRedactionRunsWithoutWorkflowSecrets now asserts no env: block is emitted when there are no secrets.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up on the test-adequacy point: bfefcc2 replaces the substring assertions with a real YAML parse of the generated step. TestSecretRedactionRunsWithoutWorkflowSecrets now asserts the env key is absent, and a new TestSecretRedactionEmitsEnvMappingWithWorkflowSecrets asserts env unmarshals as a populated mapping. I verified the test catches the original bug by temporarily reintroducing the unconditional env: write — it fails with got <nil>, confirming the null-mapping case is now detected structurally.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on one correctness issue and three test/robustness improvements.

📋 Key Themes & Highlights

Key Themes

  • Empty env: block (correctness): The refactored Go code always writes env: but only populates it when secrets exist — generating invalid YAML for secret-free workflows (the very case this PR targets).
  • TOML regex case sensitivity: The Authorization key regex is case-sensitive; lowercase variants used by some Codex versions would silently skip redaction.
  • Token length guard: The 6-char minimum applies to the full header value including the Bearer prefix, not just the credential — very loose and may capture short noise values.
  • Test fixture realism: The TOML test doesn't exercise the Bearer-prefix stripping path.

Positive Highlights

  • ✅ Removing the len(secretReferences) == 0 early-exit is the right fix and is clearly motivated
  • Set-deduplication for config paths prevents duplicate token entries
  • ✅ Good regression test in redact_secrets_test.go verifying the no-secrets path
  • ✅ Multi-log diagnostic test covers the full set of artifact files

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 39.1 AIC · ⊞ 7.1K
Comment /matt to run again

Comment thread pkg/workflow/redact_secrets.go Outdated
yaml.WriteString(" await main();\n")

// Add environment variables
yaml.WriteString(" env:\n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] Empty env: block when no secrets are declared — this generates invalid YAML with a dangling env: key that has no child entries, causing the generated workflow to fail at runtime.

💡 Suggested fix

Only emit the env: block when there is content for it:

if len(secretReferences) > 0 {
    yaml.WriteString("        env:\n")
    // ... write secret env vars ...
}

Right now env: is always written (line 167) but only populated when len(secretReferences) > 0 (line 169). A bare env: with no children is invalid YAML.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8eea487 — the env: header is now emitted only when secret variables exist, with a test asserting the block is absent otherwise.

Comment thread actions/setup/js/redact_secrets.cjs Outdated
try {
config = /** @type {Record<string, any>} */ JSON.parse(raw);
} catch {
for (const match of raw.matchAll(/\bAuthorization\s*=\s*"([^"]+)"/g)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The TOML Authorization regex is case-sensitive and only matches the key Authorization — real Codex TOML uses authorization (lower-case) in some versions. A test with authorization = "token" would currently fail silently.

💡 Suggested fix

Use a case-insensitive flag:

for (const match of raw.matchAll((bauthorization/redacted)\s*=\s*"([^"]+)"/gi)) {

Also consider adding a test with a lowercase key to document the intended contract.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c5ab33b — the TOML scan now uses the i flag, and a regression test covers a lowercase authorization key.

Comment thread actions/setup/js/redact_secrets.cjs Outdated
tokens.add(auth);
if (/^[Bb]earer /.test(auth)) {
const tokenPart = auth.slice(7).trim();
if (tokenPart.length >= 6) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] The auth.length >= 6 guard is applied to the full Authorization header value (e.g. Bearer ghs_abc123). The 6-character minimum is very loose — a malformed or short value could still be added to tokens and trigger redaction of unintended strings in logs.

💡 Suggestion

Apply the minimum length check to the extracted tokenPart only, not the raw header value (which includes the Bearer prefix). And consider a slightly higher threshold (e.g. 16) consistent with real gateway token lengths to reduce false positives:

const auth = match[1].trim();
if (/^[Bb]earer /.test(auth)) {
  const tokenPart = auth.slice(7).trim();
  if (tokenPart.length >= 16) tokens.add(tokenPart);
} else if (auth.length >= 16) {
  tokens.add(auth);
}

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c5ab33b — both the JSON and TOML paths now share one helper that strips the bearer prefix first and applies the minimum-length guard to the credential itself, so a short value can no longer pass by being prefixed. I kept the threshold at 6 rather than raising it to 16, since that value is already the JSON path's contract and existing tests depend on it; raising it is a separate behavior change. A test covers a short bearer-prefixed value producing no tokens.


it("should extract Authorization tokens from Codex TOML configuration", () => {
const configPath = path.join(tempDir, "config.toml");
fs.writeFileSync(configPath, '[mcp_servers.github]\nhttp_headers = { Authorization = "codex-gateway-token-xyz789" }\n');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The Codex TOML test uses http_headers = { Authorization = "codex-gateway-token-xyz789" } but the actual TOML format for Codex uses [tool.codex.mcp_servers.github] with http_headers.Authorization. Consider aligning the test fixture to a realistic Codex config shape to guard against format drift.

💡 Suggestion

A more realistic fixture:

[tool.codex.mcp_servers.github]
http_headers = { Authorization = "Bearer codex-gateway-token-xyz789" }

This would also exercise the Bearer-prefix stripping path, verifying both the full Bearer <token> and bare <token> end up in the token set.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c5ab33b — the fixture now matches the shape emitted by actions/setup/sh/convert_gateway_config_codex.sh ([mcp_servers.github] with url and an http_headers inline table) and uses a bearer-prefixed value, so the test asserts both the full header value and the bare token are extracted. Note Codex's generated config uses top-level [mcp_servers.*], not [tool.codex.mcp_servers.*].

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100 — Excellent

Analyzed 10 test(s): 10 design, 0 implementation, 0 violation(s).

📊 Metrics (10 tests)
Metric Value
Analyzed 10 (Go: 2, JS: 8)
✅ Design 10 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 5 (50%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Test File Classification Issues
TestSecretRedactionRunsWithoutWorkflowSecrets redact_secrets_test.go behavioral_contract, high_value None
TestSecretRedactionStepGeneration (modified) redact_secrets_test.go design_test, high_value None
extractMCPGatewayTokens – gateway-output.json redact_secrets.test.cjs behavioral_contract, high_value None
extractMCPGatewayTokens – mcp-servers.json redact_secrets.test.cjs behavioral_contract, high_value None
extractMCPGatewayTokens – Codex TOML redact_secrets.test.cjs behavioral_contract, high_value None
extractMCPGatewayTokens – full bearer + bare token redact_secrets.test.cjs behavioral_contract, high_value None
extractMCPGatewayTokens – deduplicate shared tokens redact_secrets.test.cjs design_test, edge_case None
extractMCPGatewayTokens – nonexistent config files redact_secrets.test.cjs design_test, edge_case None
extractMCPGatewayTokens – malformed JSON config redact_secrets.test.cjs design_test, edge_case None
extractMCPGatewayTokens – main() redacts all diagnostic logs redact_secrets.test.cjs behavioral_contract, high_value None

Verdict

passed. 0% implementation tests (threshold: 30%).

References: Run 31140894478

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 81.6 AIC · ⊞ 7.7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 85/100. 0% implementation tests (threshold: 30%).

@pelikhan

pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 3 commits August 7, 2026 02:48
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please triage this PR, refresh the branch if possible, address the latest reviewer feedback, and run the pr-finisher skill before handing back to maintainers.

Latest review signals:

  • Two blocking reviews called out the always-emitted empty env: block in generated YAML for secret-free workflows.
  • Please also confirm the token-extraction edge cases raised in review are either fixed or explicitly addressed.

Run context: https://github.com/github/gh-aw/actions/runs/31142787192

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 11.8 AIC · ⊞ 8.3K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI requested a review from gh-aw-bot August 7, 2026 03:16
@pelikhan
pelikhan merged commit fa72adc into main Aug 7, 2026
@pelikhan
pelikhan deleted the copilot/fix-exposed-mcp-gateway-bearer branch August 7, 2026 03:27
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.86.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diagnostic logs expose the MCP gateway agent/session bearer

4 participants