Skip to content

BYOK: suppress COPILOT_GITHUB_TOKEN injection when COPILOT_PROVIDER_BASE_URL is set#35631

Merged
pelikhan merged 3 commits into
mainfrom
copilot/fix-copilot-token-leak
May 29, 2026
Merged

BYOK: suppress COPILOT_GITHUB_TOKEN injection when COPILOT_PROVIDER_BASE_URL is set#35631
pelikhan merged 3 commits into
mainfrom
copilot/fix-copilot-token-leak

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 29, 2026

When engine: copilot is configured with a custom provider via COPILOT_PROVIDER_BASE_URL, COPILOT_GITHUB_TOKEN was unconditionally injected into the step env. The api-proxy's Copilot adapter falls back to this token as Authorization: Bearer when BYOK key resolution fails, forwarding the GitHub identity token to the user's third-party host — a credential leak that also manifests as HTTP 401s.

Changes

  • pkg/workflow/copilot_engine_execution.go

    • Detect BYOK mode early in GetExecutionSteps:
      isBYOKMode := engineEnvHasKey(workflowData, constants.CopilotProviderBaseURL)
    • Skip COPILOT_GITHUB_TOKEN injection when isBYOKMode is true; the engine.env maps.Copy that follows still lets users explicitly set it if needed
    • Remove COPILOT_GITHUB_TOKEN from the AWF --exclude-env core secret list in BYOK mode — no injection means nothing to hide
  • pkg/workflow/copilot_engine_test.goTestCopilotEngineBYOKOmitsCopilotGitHubToken

    • Token absent: COPILOT_PROVIDER_BASE_URL + COPILOT_PROVIDER_API_KEY
    • Token absent: COPILOT_PROVIDER_BASE_URL only (local/keyless provider)
    • Token present: no COPILOT_PROVIDER_BASE_URL (standard mode regression guard)

The defense-in-depth fix on the gh-aw-firewall side (getAuthHeaders() asymmetry vs getModelsFetchConfig()) is a separate follow-up in that repo.

…URL is set

When engine.env contains COPILOT_PROVIDER_BASE_URL (BYOK mode), the Copilot
execution step no longer injects COPILOT_GITHUB_TOKEN into the step environment.

Previously the token was unconditionally set and could leak to the user's
external provider via the api-proxy's fallback ****** causing HTTP 401
errors and constituting a credential-leak risk.

Changes:
- Detect isBYOKMode via engineEnvHasKey(COPILOT_PROVIDER_BASE_URL) at the
  top of GetExecutionSteps
- Skip COPILOT_GITHUB_TOKEN injection when isBYOKMode is true
- Remove COPILOT_GITHUB_TOKEN from AWF --exclude-env list in BYOK mode
  (nothing to hide when the token is not present)
- Add TestCopilotEngineBYOKOmitsCopilotGitHubToken with three sub-tests:
  token absent with URL+key, token absent with URL only, token present in
  standard mode

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix token leakage issue with COPILOT_GITHUB_TOKEN BYOK: suppress COPILOT_GITHUB_TOKEN injection when COPILOT_PROVIDER_BASE_URL is set May 29, 2026
Copilot AI requested a review from pelikhan May 29, 2026 04:56
@pelikhan pelikhan marked this pull request as ready for review May 29, 2026 05:23
Copilot AI review requested due to automatic review settings May 29, 2026 05:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Copilot engine execution so BYOK configurations using COPILOT_PROVIDER_BASE_URL do not automatically inject COPILOT_GITHUB_TOKEN, reducing the risk of forwarding a GitHub identity token to a third-party provider.

Changes:

  • Adds BYOK detection in Copilot execution based on COPILOT_PROVIDER_BASE_URL.
  • Suppresses default COPILOT_GITHUB_TOKEN env injection and AWF core secret exclusion in BYOK mode.
  • Adds unit tests for BYOK and standard-mode token presence behavior.
Show a summary per file
File Description
pkg/workflow/copilot_engine_execution.go Adds BYOK-mode gating for Copilot token injection and AWF exclude-env handling.
pkg/workflow/copilot_engine_test.go Adds regression coverage for token omission in BYOK mode and presence in standard mode.

Copilot's findings

Tip

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread pkg/workflow/copilot_engine_execution.go
Comment thread pkg/workflow/copilot_engine_execution.go
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #35631 does not have the 'implementation' label, and default business logic additions (100) do not exceed the >100 threshold.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

🧪 Test Quality Sentinel completed test quality analysis.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

PR Code Quality Reviewer completed the code quality review.

@github-actions github-actions Bot mentioned this pull request May 29, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Two blocking security issues need resolution before merge.

### Findings

1. (HIGH) engine.env merge re-injects COPILOT_GITHUB_TOKEN in BYOK mode — inline comment added at line 365.

The maps.Copy(env, workflowData.EngineConfig.Env) that runs after the conditional injection guard will silently overwrite the omission if the user has COPILOT_GITHUB_TOKEN set in engine.env (e.g. a leftover from a non-BYOK configuration). This defeats the entire purpose of the PR. Fix: delete(env, "COPILOT_GITHUB_TOKEN") after the merge when isBYOKMode.

2. (HIGH) BYOK detection predicate is inconsistent with GetSecretValidationStep — already flagged in the existing review thread at line 54.

isBYOKMode fires on COPILOT_PROVIDER_BASE_URL alone, but secret validation only skips when COPILOT_PROVIDER_API_KEY or COPILOT_PROVIDER_BEARER_TOKEN is present. A COPILOT_PROVIDER_BASE_URL-only workflow will skip token injection here but still fail the activation job's secret-validation step — or, if validation logic is later relaxed, will run with neither GitHub token nor BYOK credentials.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 1.6M

"GITHUB_API_URL": "${{ github.api_url }}",
}
// Inject the GitHub token only when not in BYOK mode. The engine.env merge that
// happens later (maps.Copy(env, workflowData.EngineConfig.Env)) can still override
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.

engine.env merge can silently re-inject COPILOT_GITHUB_TOKEN in BYOK mode: the maps.Copy(env, workflowData.EngineConfig.Env) that runs after this guard will overwrite the intentional omission if the user has COPILOT_GITHUB_TOKEN in engine.env — e.g. a leftover value from a prior non-BYOK config — forwarding the GitHub identity token to the third-party provider and recreating the exact credential-leak this PR aims to prevent.

💡 Suggested fix

After the maps.Copy merge, enforce the invariant unconditionally in BYOK mode:

maps.Copy(env, workflowData.EngineConfig.Env)
if isBYOKMode {
    delete(env, "COPILOT_GITHUB_TOKEN")
}

This makes the security property hold regardless of what the user put in engine.env, and avoids relying on the user knowing to clean up old config. The comment on line 362 currently acknowledges this gap ("can still override") but does not mitigate it.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnose and /tdd — approving with two minor test-coverage suggestions.

📋 Key Themes & Highlights

Positive Highlights

  • ✅ Clean fix at the right abstraction level — isBYOKMode is computed once and propagated consistently across all three mutation sites (env injection, --exclude-env list, log message)
  • ✅ Regression guard in the standard-mode sub-test is exactly right; it would catch accidental removal of the token in the default path
  • engineEnvHasKey returns false when workflowData/EngineConfig is nil — no null-pointer risk
  • ✅ Debug log line on the BYOK skip path aids future troubleshooting
  • ✅ The #nosec G101 annotation is retained where needed

Suggested improvements (non-blocking)

  1. --exclude-env path not tested — env injection and AWF exclude-env are independent code paths; see inline comment on line 1903
  2. User-override escape hatch not tested — the documented engine.env override behaviour has no regression guard; see inline comment on line 1887

Residual exposure (known, deferred)

The PR correctly notes that the firewall-side fix (getAuthHeaders asymmetry) is a separate follow-up. The suppression here is the right first layer of defense.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 1.5M

if !strings.Contains(stepContent, "COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}") {
t.Errorf("COPILOT_GITHUB_TOKEN should be present in standard (non-BYOK) mode, got:\n%s", stepContent)
}
})
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 copilotCoreSecrets/--exclude-env mutation site is not covered.

The BYOK subtests verify that COPILOT_GITHUB_TOKEN: is absent from the env block, but they don't check that --exclude-env COPILOT_GITHUB_TOKEN is also absent from the AWF command. These are two independent code paths — a regression in one would not be caught by the other.

💡 Suggested additions to each BYOK sub-test
// --exclude-env COPILOT_GITHUB_TOKEN must not appear in the AWF command in BYOK mode
if strings.Contains(stepContent, "--exclude-env COPILOT_GITHUB_TOKEN") {
    t.Errorf("--exclude-env COPILOT_GITHUB_TOKEN should be absent in BYOK mode, got:\n%s", stepContent)
}

And complement the standard-mode sub-test:

if !strings.Contains(stepContent, "--exclude-env COPILOT_GITHUB_TOKEN") {
    t.Errorf("--exclude-env COPILOT_GITHUB_TOKEN should be present in standard mode")
}

}
})

t.Run("COPILOT_GITHUB_TOKEN present when COPILOT_PROVIDER_BASE_URL is not set (standard mode)", func(t *testing.T) {
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] Consider adding a sub-test for the documented user-override behaviour.

The PR description notes: "the engine.env maps.Copy that follows still lets users explicitly set [COPILOT_GITHUB_TOKEN] if needed." This intentional escape hatch isn't tested, so a future refactor could silently break it or inadvertently suppress it.

💡 Suggested sub-test skeleton
t.Run("COPILOT_GITHUB_TOKEN present when user explicitly sets it in BYOK engine.env", func(t *testing.T) {
    workflowData := &WorkflowData{
        Name: "test-workflow",
        EngineConfig: &EngineConfig{
            Env: map[string]string{
                constants.CopilotProviderBaseURL: "(api.openai.com/redacted),
                "COPILOT_GITHUB_TOKEN":            "${{ secrets.MY_OVERRIDE }}",
            },
        },
    }
    steps := engine.GetExecutionSteps(workflowData, "/tmp/gh-aw/test.log")
    stepContent := strings.Join([]string(steps[0]), "\n")
    if !strings.Contains(stepContent, "COPILOT_GITHUB_TOKEN: ${{ secrets.MY_OVERRIDE }}") {
        t.Errorf("explicit user override of COPILOT_GITHUB_TOKEN should survive BYOK suppression")
    }
})

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Excellent

Analyzed 3 test(s) across 1 test function (TestCopilotEngineBYOKOmitsCopilotGitHubToken): 3 design tests, 0 implementation tests, 0 guideline violations.

📊 Metrics & Test Classification (3 tests analyzed)
Metric Value
New/modified tests analyzed 3
✅ Design tests (behavioral contracts) 3 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 3 (100%)
Duplicate test clusters 0
Test inflation detected ⚠️ Yes (72 test lines / 28 production lines ≈ 2.6:1)
🚨 Coding-guideline violations 0

Test Classification Details

Test File Classification Issues Detected
"COPILOT_GITHUB_TOKEN absent when COPILOT_PROVIDER_BASE_URL is set (BYOK)" pkg/workflow/copilot_engine_test.go ✅ Design None
"COPILOT_GITHUB_TOKEN absent with COPILOT_PROVIDER_BASE_URL only (no API key)" pkg/workflow/copilot_engine_test.go ✅ Design None
"COPILOT_GITHUB_TOKEN present when COPILOT_PROVIDER_BASE_URL is not set (standard mode)" pkg/workflow/copilot_engine_test.go ✅ Design None

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 3 tests — unit (//go:build !integration)
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%).

The three sub-tests inside TestCopilotEngineBYOKOmitsCopilotGitHubToken enforce a clear security behavioral contract: the COPILOT_GITHUB_TOKEN credential must not be forwarded to a third-party provider endpoint. All three scenarios (BYOK with API key, BYOK without API key, standard mode) are covered with real component interactions — no mocks, correct build tag, meaningful assertion messages.

i️ Minor note: Test inflation ratio is ~2.6:1 (72 test lines vs 28 production lines). This is acceptable here because the extra lines are three distinct security scenarios that exhaustively cover the behavioral contract — not boilerplate copy-paste. The 10-point inflation deduction was applied per rubric.

📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 1.2M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 80/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All 3 sub-tests enforce the BYOK security behavioral contract with real component interactions.

@github-actions
Copy link
Copy Markdown
Contributor

@copilot refresh the branch and rerun checks, then summarize any remaining blockers.

Generated by 👨‍🍳 PR Sous Chef · gpt54 14.1M ·

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

@copilot refresh the branch and rerun checks, then summarize any remaining blockers.

Generated by 👨‍🍳 PR Sous Chef · gpt54 14.1M ·

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments and reviews from copilot and GitHub-actions actor

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 29, 2026

@copilot review all comments and reviews from copilot and GitHub-actions actor

Addressed in commit 4cc6460.

  • Aligned Copilot secret validation with BYOK base-url detection, so COPILOT_PROVIDER_BASE_URL alone now skips COPILOT_GITHUB_TOKEN validation.
  • Added regression coverage for firewall-wrapped execution to assert BYOK mode omits --exclude-env COPILOT_GITHUB_TOKEN in the generated AWF command.

@pelikhan pelikhan merged commit 5925239 into main May 29, 2026
25 checks passed
@pelikhan pelikhan deleted the copilot/fix-copilot-token-leak branch May 29, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BYOK: COPILOT_GITHUB_TOKEN is auto-injected even when COPILOT_PROVIDER_BASE_URL is set, leaking as fallback Bearer to external providers (HTTP 401)

3 participants