Skip to content

[aw] Decouple Process Safe Outputs client token from project-specific token#45999

Merged
pelikhan merged 18 commits into
mainfrom
copilot/aw-safe-outputs-update-project-token-leaking
Jul 16, 2026
Merged

[aw] Decouple Process Safe Outputs client token from project-specific token#45999
pelikhan merged 18 commits into
mainfrom
copilot/aw-safe-outputs-update-project-token-leaking

Conversation

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

update-project.github-token was being promoted to the shared Process Safe Outputs step client token, overriding safe-outputs.github-token and causing non-project handlers to run under the wrong credential. This change restores token separation: shared safe-output handlers use the safe-outputs token path, while project handlers keep using the dedicated project token channel.

  • Token precedence fix (compiler)

    • Updated handler-manager step token selection to stop preferring project token for with.github-token.
    • with.github-token now follows safe-outputs precedence only:
      1. safe-outputs.github-token
      2. fallback (secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN)
  • Project token isolation preserved

    • Kept GH_AW_PROJECT_GITHUB_TOKEN wiring unchanged for project operations.
    • Project handlers continue receiving project credentials via env/config, not by elevating the shared github-script client.
  • Focused regression coverage

    • Updated and expanded handler-manager token tests to assert split-token behavior:
      • project token present in GH_AW_PROJECT_GITHUB_TOKEN
      • with.github-token stays on safe-outputs token/fallback
      • mixed config case (safe-outputs.github-token + update-project.github-token) behaves correctly
// before: project token could override shared client token
if projectToken != "" {
    configToken = projectToken
} else if data.SafeOutputs != nil && data.SafeOutputs.GitHubToken != "" {
    configToken = data.SafeOutputs.GitHubToken
}

// after: shared client token follows safe-outputs precedence only
if data.SafeOutputs != nil && data.SafeOutputs.GitHubToken != "" {
    configToken = data.SafeOutputs.GitHubToken
}

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix token leaking in safe outputs for update-project [aw] Decouple Process Safe Outputs client token from project-specific token Jul 16, 2026
Copilot AI requested a review from pelikhan July 16, 2026 13:42
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot add tests for all safe outputs to prevent github token pollution

Copilot AI and others added 2 commits July 16, 2026 13:48
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@copilot add tests for all safe outputs to prevent github token pollution

Added comprehensive regression coverage in 65dfd4e: new TestHandlerManagerGitHubTokenIsolationAcrossSafeOutputHandlers iterates all registered safe-output handlers and verifies with.github-token stays on safe-outputs.github-token while project token remains isolated to GH_AW_PROJECT_GITHUB_TOKEN.

@pelikhan
pelikhan marked this pull request as ready for review July 16, 2026 14:27
Copilot AI review requested due to automatic review settings July 16, 2026 14:27
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

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

No ADR enforcement needed: PR #45999 does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (75 additions detected, threshold is 100).

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

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

This PR updates the gh-aw workflow compiler to prevent project-specific credentials (e.g., safe-outputs.update-project.github-token) from overriding the shared Process Safe Outputs step’s with.github-token, restoring intended token separation between shared safe-output processing and project operations.

Changes:

  • Updated buildHandlerManagerStep token selection so with.github-token follows safe-outputs precedence only (safe-outputs token → magic fallback).
  • Updated/expanded Go tests to assert split-token behavior between GH_AW_PROJECT_GITHUB_TOKEN and the shared with.github-token.
  • Regenerated the smoke workflow lockfile to reflect the updated token wiring.
Show a summary per file
File Description
pkg/workflow/safe_outputs_handler_manager_token_test.go Updates and expands regression tests to ensure project token stays isolated from the shared handler-manager with.github-token.
pkg/workflow/compiler_safe_outputs_steps.go Changes the compiler’s handler-manager step to stop preferring the project token for the shared github-script client.
.github/workflows/smoke-project.lock.yml Regenerated workflow output showing the shared github-token now uses the safe-outputs fallback chain.

Review details

Tip

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

  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines 274 to +276
// With section for github-token
// Use the standard safe outputs token for all operations.
// If project operations are configured, prefer the project token for the github-script client.
// Rationale: update_project/create_project_status_update call the Projects v2 GraphQL API, which
// cannot be accessed with the default GITHUB_TOKEN. GH_AW_PROJECT_GITHUB_TOKEN is the required
// token for Projects v2 operations.
// Use the standard safe-outputs token for the shared github-script client.
// Project operations use GH_AW_PROJECT_GITHUB_TOKEN from env with dedicated handler logic.

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.

Addressed in 60abf42 and prior commits: runtime project-handler auth is now wired through per-handler token fallback/client rebinding, so project operations no longer depend on the shared step-level client token path.

@github-actions github-actions Bot mentioned this pull request Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100 — Excellent

Analyzed 3 test function(s) (19+ subtests via table-driven): 3 design, 0 implementation, 0 violation(s).

📊 Metrics (3 tests)
Metric Value
Analyzed 3 (Go: 3, JS: 0)
✅ Design 3 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 3 (100%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Test File Classification Issues
TestHandlerManagerGitHubTokenEnvVarForCrossRepo safe_outputs_handler_manager_token_test.go design_test · behavioral_contract None
TestHandlerManagerProjectGitHubTokenEnvVar safe_outputs_handler_manager_token_test.go design_test · behavioral_contract None
TestHandlerManagerGitHubTokenIsolationAcrossSafeOutputHandlers safe_outputs_handler_manager_token_test.go design_test · behavioral_contract None

Verdict

Passed. 0% implementation tests (threshold: 30%). All 3 tests enforce behavioral contracts with both positive and negative assertions, good edge/precedence coverage, valid build tags, and no mock violations.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 22.7 AIC · ⌖ 8.54 AIC · ⊞ 6.8K ·
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: 82/100. 0% implementation tests (threshold: 30%).

@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 — approving with one suggestion on test robustness.

Key Themes & Highlights

Positive Highlights

  • Root cause properly addressed: the project-token-preference branch is cleanly removed; token precedence is now unambiguous.
  • Minimal, surgical change in compiler_safe_outputs_steps.go — only the two lines causing the bug are removed.
  • Comment block updated to accurately reflect the new precedence order.
  • Smoke-test lock file updated in lock-step, showing the fix propagates end-to-end.
  • New isolation test iterates over handlerRegistry dynamically, staying complete as new handlers are added.

Minor Concern (see inline comment)

Empty-map configs (map[string]any{}) in the new isolation test may silently skip handlers that need required fields, giving false confidence. A one-line assert.Contains(t, yamlStr, handlerName) guard would close this gap.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 24.3 AIC · ⌖ 4.45 AIC · ⊞ 6.7K
Comment /matt to run again

"project": "https://github.com/orgs/myorg/projects/1",
},
}

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 empty-map handler entries (map[string]any{}) in the new isolation test may silently fail to parse for handlers that require specific config fields, meaning the sub-test only truly exercises the update-project token path rather than the named handler's code.

💡 Suggestion

Consider using a minimal valid config for each handler, or at least assert that the compiled step includes the expected handler entry (not just the token line). For example:

// Verify handler is actually present in the compiled YAML, not silently skipped
assert.Contains(t, yamlStr, handlerName, "handler %q should appear in compiled step", handlerName)

This would catch cases where an empty config causes a handler to be omitted entirely, giving you false confidence in the isolation property.

@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.

Addressed in 60abf42 and prior commits: the test now avoids vacuous passes by explicitly checking for the handler config key in GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG and skipping isolation assertions when a handler is not materialized from minimal config.

@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: Two test validity concerns — non-blocking

The compiler change itself is correct: removing project token from with.github-token precedence is the right fix, and the lock file update is consistent.

Findings

Medium — False-positive assertion in parameterized test
assert.Contains(t, yamlStr, "GH_AW_PROJECT_GITHUB_TOKEN: ..." passes unconditionally for every handler sub-test because update-project is always present in the shared fixture, not because the handler under test produces it. See inline comment.

Medium — Empty handler configs may be silently dropped
Handlers added as map[string]any{} may be no-ops if extractSafeOutputsConfig requires mandatory fields — making the isolation assertions pass vacuously. See inline comment.

Both are test-quality issues only; the production logic change is sound.

🔎 Code quality review by PR Code Quality Reviewer · 26.5 AIC · ⌖ 4.51 AIC · ⊞ 5.6K
Comment /review to run again

Comments that could not be inline-anchored

pkg/workflow/safe_outputs_handler_manager_token_test.go:391

False-positive assertion — the project token check passes regardless of the handler under test.

<details>
<summary>💡 Details</summary>

The assertion assert.Contains(t, yamlStr, &quot;GH_AW_PROJECT_GITHUB_TOKEN: ${{ secrets.PROJECTS_PAT }}&quot;) fires for every handler in the registry. It always passes because update-project is unconditionally present in the safeOutputs fixture for every sub-test — not because the handler under test emits the expected env var. This means the assertion provid…

pkg/workflow/safe_outputs_handler_manager_token_test.go:357

Empty handler config may be silently dropped, making the isolation test hollow for most handlers.

<details>
<summary>💡 Details</summary>

The test adds each non-update-project handler as map[string]any{} (empty config). If extractSafeOutputsConfig silently skips handlers with missing required fields, those handlers are never wired into the compiled output. The isolation assertions then pass vacuously — they're not proving the handler uses the right token; they're just confirming an …

@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: Token decoupling correctness gap

The compiler change is the right direction — decoupling the step-level with.github-token from the project PAT. However there is a blocking correctness regression in the runtime path.

Root issue: update_project and other project handlers obtain their GitHub client via global.github (bound to with.github-token) when no per-handler github-token is in their config (see handler_auth.cjs line 44-46 and update_project.cjs line 1263). GH_AW_PROJECT_GITHUB_TOKEN is set as a step env var, but is only used for error diagnostics — it never creates an Octokit instance.

Who is affected: Any user who configures update-project.project relying on secrets.GH_AW_PROJECT_GITHUB_TOKEN without explicitly setting update-project.github-token. Their project GraphQL calls will silently use the wrong token.

See the inline comment on the changed block for the specific fix.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 78 AIC · ⌖ 4.53 AIC · ⊞ 5K

// 3. Magic secret fallback via getEffectiveSafeOutputGitHubToken()
// 1. Safe-outputs level token (so.GitHubToken)
// 2. Magic secret fallback via getEffectiveSafeOutputGitHubToken()
//

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.

Correctness regression: project handlers lose project PAT when no per-handler github-token is configured

This change decouples the step-level with.github-token from the project PAT, but the project handlers (update_project, create_project_status_update, create_project) still rely on global.github (initialized from with.github-token) when no per-handler github-token is set.

Broken scenario:

safe-outputs:
  update-project:
    project: https://github.com/orgs/myorg/projects/1
    # No github-token — user expects secrets.GH_AW_PROJECT_GITHUB_TOKEN to be used

Previously, with.github-token was elevated to secrets.GH_AW_PROJECT_GITHUB_TOKEN, giving global.github Projects v2 GraphQL access. After this change, with.github-token = safe-outputs/fallback token, and GH_AW_PROJECT_GITHUB_TOKEN is only an env var used in error messages — not used to initialize any Octokit client. update_project.main() falls back to global.github (update_project.cjs line 1263), which now lacks project access.

Suggested fix: In safe_output_handler_manager.cjs, when building handlerConfig for project-type handlers and the config has no explicit github-token, inject process.env.GH_AW_PROJECT_GITHUB_TOKEN as handlerConfig['github-token']. This preserves backward compatibility without re-coupling the step-level token.

Also, the comment in handler_auth.cjs line 17 is now stale (project token > global safe-outputs token) and needs updating.

@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.

Addressed in 60abf42 and prior commits: project handlers now get GH_AW_PROJECT_GITHUB_TOKEN as per-handler fallback when github-token is unset, are initialized with a dedicated Octokit client, and execute with temporary global.github rebinding so project GraphQL calls do not fall back to the shared safe-outputs client token.

Copilot AI and others added 2 commits July 16, 2026 16:20
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix https://github.com/github/gh-aw/actions/runs/29514564855/job/87676214531?pr=45999

Fixed in e21b4d9. I reproduced the failing CJS job locally and patched actions/setup/js/safe_output_handler_manager.cjs to satisfy @ts-check for global.github/global.getOctokit usage without changing runtime behavior. The failing CI steps now pass locally (make check-cjs-syntax and cd actions/setup/js && npm run typecheck).

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review feedback, and rerun checks once the branch is up to date.

Generated by 👨‍🍳 PR Sous Chef · 9.89 AIC · ⌖ 7.32 AIC · ⊞ 5.1K ·
Comment /souschef to run again

…ts-update-project-token-leaking

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address unresolved review feedback, and rerun checks once the branch is up to date.

...

Done in 34ad4707bc.

  • Ran the PR-finisher pass and updated the branch to latest main (branch is now up to date).
  • Reviewed unresolved threads and prior CI failures; the previously reported CJS/typecheck and handler issues are already addressed in earlier commits on this PR.
  • Re-ran local checks (make fmt, make lint, make test-unit, make test, make recompile) and make agent-report-progress; the remaining failures are pre-existing pkg/cli environment-dependent test failures in this sandbox (TestLogsJSONOutputWithNoRuns, TestCloseExistingOrgIssuesByMarkerSkipsPRsAndPaginates, TestRunWorkflowOnGitHub_FlagCombinations).
  • Ran parallel_validation; Code Review surfaced non-actionable findings on upstream workflow files introduced by the merge, and CodeQL timed out per tool output.

@pelikhan

Copy link
Copy Markdown
Collaborator

Copilot AI commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix https://github.com/github/gh-aw/actions/runs/29519013957/job/87705368761

Fixed in 6e445b0.

I investigated job 87705368761 and patched actions/setup/js/safe_output_handler_manager.test.cjs so the mentions-config assertions match the current main(handlerConfig, handlerGithubClient) call shape (including the second null client arg for non-token handlers). I reran the target test successfully:

GH_AW_PROMPTS_DIR=/home/runner/work/gh-aw/gh-aw/actions/setup/md npm test -- safe_output_handler_manager.test.cjs

@pelikhan
pelikhan merged commit 38e22c4 into main Jul 16, 2026
36 of 37 checks passed
@pelikhan
pelikhan deleted the copilot/aw-safe-outputs-update-project-token-leaking branch July 16, 2026 19:17
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.11

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.

[aw] Safe-outputs update-project token leaking into other outputs

4 participants