github / github/gh-aw

Support secret-manager-backed GitHub App credentials without masked cross-job outputs

Open
#33,592 0 comments 0 reactions 0 assignees View on GitHub
ai-inspected community
Dominant language
Go
Stars
5.1k
Forks
541
Avg merge
5h 46m
Merged PRs (30d)
760

Description

## Summary

Please add first-class support for GitHub App credentials that are fetched from an external secret manager at runtime, without requiring the app private key to be stored directly in GitHub Actions secrets.

Today gh-aw supports GitHub App authentication through `github-app:` frontmatter:

```yaml
github-app:
app-id: ${{ vars.WORKFLOW_APP_ID }}
private-key: ${{ secrets.WORKFLOW_APP_PRIVATE_KEY }}
owner: my-org
repositories: ["*"]
```

This works when the app id/private key are directly available as GitHub Actions vars/secrets. It does not work cleanly when an organization requires GitHub App credentials to live in an external secret manager such as Conjur, Vault, Keeper, or a similar enterprise secret-management system.

## Prior Art / Related Issues

I searched existing issues and did not find an exact duplicate for this secret-manager-backed GitHub App credential flow.

Related but different issues:

- #30780: `on.needs` custom jobs can produce invalid workflow syntax.
- #30790: activation `needs` gaps for `engine.env` expressions.
- #21188: cross-repo activation checkout with `on.github-app`.
- #18379: GitHub App auth for Copilot requests, but `COPILOT_GITHUB_TOKEN` still requires a PAT and is not this issue.

## Use Case

Some deployments do not allow long-lived GitHub App private keys to be stored in GitHub Actions secrets. Instead:

- GitHub Actions stores only secret-manager bootstrap credentials.
- GitHub App credentials live in an external secret manager.
- Workflows fetch `WORKFLOW_APP_ID` and `WORKFLOW_APP_PRIVATE_KEY` at runtime.
- gh-aw should then mint short-lived GitHub App installation tokens for checkout, GitHub tools, and safe outputs.

## Current Behavior

A natural gh-aw configuration is to use a custom credential-fetching job and wire it through `needs`:

```yaml
on:
workflow_dispatch:
needs: [secrets_fetcher]

jobs:
secrets_fetcher:
runs-on: ubuntu-latest
outputs:
app_id: ${{ steps.fetch.outputs.WORKFLOW_APP_ID }}
private_key: ${{ steps.fetch.outputs.WORKFLOW_APP_PRIVATE_KEY }}
steps:
- id: fetch
uses: some-secret-manager-action@v1
with:
# Fetch WORKFLOW_APP_ID and WORKFLOW_APP_PRIVATE_KEY from an external secret manager.

safe-outputs:
needs: [secrets_fetcher]
github-app:
app-id: ${{ needs.secrets_fetcher.outputs.app_id }}
private-key: ${{ needs.secrets_fetcher.outputs.private_key }}
```

This can compile, but it fails at runtime with many secret-manager actions because they mask retrieved values using `::add-mask::` / `core.setSecret()`.

GitHub Actions then refuses to expose masked values as job outputs:

```text
Skip output 'app_id' since it may contain secret.
Skip output 'private_key' since it may contain secret.
```

Then gh-aw's generated GitHub App token-minting step receives empty inputs and fails:

```text
The 'client-id' (or deprecated 'app-id') input must be set to a non-empty string.
```

## Root Cause

gh-aw correctly mints GitHub App installation tokens inside each generated job that needs them. That is the right security model.

The gap is that secret-manager-fetched credentials cannot safely cross job boundaries as `needs..outputs.*` once the secret-fetching action masks them.

The pattern works only when the secret-fetch step and `actions/create-github-app-token` run in the same job:

```yaml
- id: get-secrets
uses: some-secret-manager-action@v1

- id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ steps.get-secrets.outputs.WORKFLOW_APP_ID }}
private-key: ${{ steps.get-secrets.outputs.WORKFLOW_APP_PRIVATE_KEY }}
```

But gh-aw owns the generated token-minting jobs/steps, so workflow authors cannot express: "run this credential-fetch step immediately before every generated GitHub App token mint."

## Expected Behavior

gh-aw should provide a first-class way to fetch GitHub App credentials inside every generated job that needs to mint an app token, without passing masked secrets through job outputs.

The app token itself should still be generated by gh-aw using `actions/create-github-app-token`. The feature should only solve credential provisioning.

## Proposed Design

Add support for credential-provider steps on `github-app:` configs.

Example syntax:

```yaml
github-app:
credentials:
steps:
- name: Fetch GitHub App credentials
id: app-creds
uses: some-secret-manager-action@v1
with:
# Fetch WORKFLOW_APP_ID and WORKFLOW_APP_PRIVATE_KEY.
app-id: ${{ steps.app-creds.outputs.WORKFLOW_APP_ID }}
private-key: ${{ steps.app-creds.outputs.WORKFLOW_APP_PRIVATE_KEY }}
owner: my-org
repositories: ["*"]
```

The compiler would inject `credentials.steps` into each generated job immediately before any generated `actions/create-github-app-token` step for that `github-app` config.

This avoids cross-job masked output loss because `steps.app-creds.outputs.*` is consumed within the same job.

## Scope

This should apply consistently to all gh-aw token-minting sites:

1. `checkout.github-app`
2. `tools.github.github-app`
3. `safe-outputs.github-app`
4. top-level `github-app` fallback
5. `on.github-app` / activation token minting

## Implementation Plan

Please implement the following changes:

1. **Extend GitHub App configuration**
- Update `GitHubAppConfig` in `pkg/workflow/safe_outputs_app_config.go` with an optional credential-provider config.
- Suggested shape: `credentials.steps`, `credentials.app-id`, `credentials.private-key`.
- Preserve existing `app-id` / `client-id` and `private-key` behavior for direct vars/secrets.

2. **Update schema and validation**
- Update frontmatter schema files to accept `github-app.credentials` wherever `github-app` is valid.
- Validate that `credentials.steps` is only emitted into generated jobs and is not interpreted as normal workflow-level steps.
- Validate that `credentials.app-id` and `credentials.private-key` reference same-job step outputs, for example `${{ steps.app-creds.outputs.WORKFLOW_APP_ID }}`.
- Reject or warn on `needs.*.outputs.*` in `credentials.app-id` / `credentials.private-key`, because that recreates the masked-output problem.
- Follow the existing error-message style: `[what's wrong]. [what's expected]. [example]`.

3. **Inject credential-provider steps before token minting**
- Update generated token-minting paths so credential-provider steps appear immediately before `actions/create-github-app-token`.
- Ensure this works for checkout token minting, GitHub MCP/tool token minting, safe-output token minting, and activation token minting.
- Ensure step IDs remain collision-safe if multiple GitHub App configs are used in one workflow.

4. **Preserve existing security model**
- gh-aw should still mint short-lived installation tokens with `actions/create-github-app-token`.
- The agent job should still receive only the minted token where appropriate, not the raw private key.
- Safe outputs should keep existing least-privilege permission calculation.
- Existing `ignore-if-missing` behavior should continue to work for direct GitHub Actions vars/secrets. Define and document whether it also applies to credential-provider outputs.

5. **Add tests**
- Add unit tests for parsing and validating `github-app.credentials`.
- Add compiler-output tests showing credential steps emitted before every relevant token-minting step.
- Add regression tests proving that direct `github-app.app-id/private-key` configurations compile unchanged.
- Add negative tests for invalid `credentials` shapes and `needs.*.outputs.*` references in credential fields.

6. **Update documentation**
- Update `docs/src/content/docs/reference/auth.mdx` with a section like "Secret-manager-backed GitHub App credentials".
- Mention why job outputs from masked secrets are not reliable across job boundaries.
- Provide a generic external secret-manager example.
- Cross-link from `docs/src/content/docs/reference/triggers.md` and `docs/src/content/docs/reference/safe-outputs.md` where `on.needs` / `safe-outputs.needs` currently show secret-fetching examples, clarifying that masked credentials should use the new same-job credential-provider mechanism instead.

7. **Run standard validation**
- Run the project validation flow recommended in `CONTRIBUTING.md`, including `make agent-finish`.

## Acceptance Criteria

- A workflow can keep GitHub App id/private key in an external secret manager.
- GitHub Actions secrets only need secret-manager bootstrap credentials.
- gh-aw still mints GitHub App installation tokens itself.
- No app private key is passed through `needs..outputs.*`.
- Generated workflows do not require manual lockfile patching.
- Existing `github-app` behavior using `vars.*` / `secrets.*` remains unchanged.
- Documentation clearly explains when to use direct vars/secrets vs same-job credential-provider steps.

## Why This Matters

This enables enterprise CentralRepoOps and cross-repository workflows where GitHub App credentials must remain in approved secret managers, while still using gh-aw's native GitHub App token model and safe-output permission boundaries.

## Follow-up Validation

A throwaway validation confirmed that the workaround is viable when the GitHub App credentials are fetched from the external secret manager inside each job immediately before `actions/create-github-app-token` runs.

The gh-aw-generated token-minting sites that needed same-job credential access were:

- activation checkout token
- agent checkout token
- agent GitHub/MCP token
- safe_outputs token
- conclusion token

With that pattern, cross-repository checkout, agent execution, PR review submission, and label updates all completed successfully.

This reinforces the feature request: secret-manager-backed GitHub App credentials need to be injectable at every gh-aw-generated token-minting site, without requiring masked values to cross job boundaries.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.