feat(engine): GitHub App-backed Copilot engine auth#1326
Conversation
3c28178 to
c06071d
Compare
|
Rebased onto
Validation re-run green after rebase: |
🔍 Rust PR ReviewSummary: Looks good with one maintainability concern worth noting. Well-designed feature overall — strong security posture, thorough validation, and solid test coverage across all targets. Findings
|
c06071d to
fb3d5b2
Compare
|
Addressed the fragile display-name dedup finding. Why not traverse the graph? The IR dependency graph ( Fix: stop inspecting steps entirely. Whether the Agent job stages the bundle is a pure function of front matter, so I made
Verified a github-app-token-only workflow (no other bundle feature) compiles to exactly 2 downloads (Agent + Detection), 2 mint + 2 revoke. Gate green: The second suggestion (validate-at-deserialize) is left as a separate optional follow-up. |
🔍 Rust PR ReviewSummary: Implementation is solid with good security controls — one actionable correctness concern to address before merge. Findings🐛 Bugs / Logic Issues
|
Add engine.github-app-token front-matter to mint a GitHub App installation token for the Copilot engine (issue #1316), source-clean and without a VSIX or ADO service connection. A new github-app-token ado-script bundle builds an RS256 JWT from the App ID + private key (ADO secret variables), resolves the installation for the owner, exchanges it for an installation access token (optionally repo-scoped), and exposes it as a masked same-job GITHUB_APP_TOKEN. The compiler wires it into GITHUB_TOKEN for the Copilot engine only, in the Agent and Detection jobs; it is never provided to SafeOutputs, user steps, ManualReview, Teardown, or Conclusion. A best-effort revoke step deletes the token after the run. Schema: app-id (literal or variable name), private-key (secret variable), owner, optional repositories, optional api-url (GHES), skip-token-revocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses PR review feedback: - Pin GH_APP_OUTPUT_VAR to the compiler constant (GITHUB_APP_TOKEN) in the mint step. ADO injects pipeline variables as env vars, so without an explicit step-level env entry a pipeline variable named GH_APP_OUTPUT_VAR could silently redirect the minted token, leaving $(GITHUB_APP_TOKEN) empty and breaking auth with no mint-time error. Step env overrides the pipeline-var injection, closing the hole. - Add cfg.validate() to github_app_token_revoke_step_typed for symmetry with the mint step, so neither assumes the other ran first. - Document why the revoke step intentionally omits `set -eo pipefail` (best-effort cleanup; bundle revoke always exits 0; continueOnError). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Strengthens the GH_APP_OUTPUT_VAR fix (PR review): rather than pinning the output-variable name in the step env, move ALL non-secret github-app-token inputs to single-quoted argv flags (--app-id, --owner, --output-var, --repositories, --api-url). ADO injects pipeline variables into a step's env, so any env-sourced knob is shadowable; argv comes only from the compiler- authored script and cannot be shadowed. This eliminates the class of bug at the root for this bundle, not just the one instance. Only the two secrets stay in masked env (GH_APP_PRIVATE_KEY on mint, GH_APP_TOKEN on revoke) — a secret must never appear on a command line. Values are single-quoted (with '\'' escaping) so metacharacters and the runtime- substituted variable-form app-id value cannot expand or word-split. Bundle main()/revoke() now take a parsed CliArgs; env is read only for the secret. Updated Rust + vitest tests and docs accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
de78ab8 to
3e49b87
Compare
🔍 Rust PR ReviewSummary: Solid implementation — the security design is well-reasoned and thoroughly tested. One forward-compatibility trap in the TypeScript arg parser worth fixing, and a few minor observations. Findings
|
Addresses PR review feedback: - parseArgs (github-app-token bundle): never consume a `--flag`-looking token as the value of a preceding flag. Keeps parsing aligned if a future compiler emits a value-less boolean flag (e.g. --debug), instead of silently swallowing the next real flag. Added vitest cases. - Detection job: note that its inline install_and_download_steps_typed is the only ado-script download path there, so a future feature should hoist a single gated download rather than add a second. - Emit a non-blocking compile advisory when engine.github-app-token is configured, reminding the author to store the private-key variable as a secret (the compiler validates the name but cannot verify secrecy). Added unit tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid implementation with one logic gap worth fixing and one minor error message inaccuracy. Findings🐛 Bugs / Logic Issues
|
Aligns github-app-token with ado-aw's GITHUB_TOKEN contract ("the compiler owns
the variable name; the user owns the value"):
- private-key is now OPTIONAL, defaulting to the compiler-owned secret variable
GITHUB_APP_PRIVATE_KEY (set via `ado-aw secrets set`). Set it only to override
with a differently-named secret. The common case needs no private-key line.
- app-id is a literal-only value (numeric App ID or alphanumeric client ID). The
variable-name form is removed, which also fixes a latent bug: the old
"all-digits => literal, else variable" heuristic mis-read client IDs like
`Iv23li...` as variable names and rendered `$(Iv23li...)`, breaking auth.
Output variable stays the fixed internal constant GITHUB_APP_TOKEN. Updated
validation, the secrecy advisory (names the effective variable), tests, and
docs/engine.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good overall — solid security design and thorough test coverage. One documentation bug in Findings🐛 Bugs / Logic Issues
|
Addresses PR review: engine.github-app-token was silently a no-op for a non-Copilot engine — the mint/revoke steps emit based on github_app_token().is_some() (engine-agnostic), but GITHUB_TOKEN is only sourced from $(GITHUB_APP_TOKEN) on the Copilot path (copilot_env). Add validate_engine_feature_support: a hard compile error when github-app-token is set with a non-copilot engine.id, run early in compile_pipeline_inner so the author gets a precise message. Mirrors gh-aw's engine-gated config validation (e.g. max-tool-denials, which errors "supported only with engine 'copilot'"). ado-aw only supports copilot today (get_engine rejects others), so this is primarily future-proofing + a precise error, and the load-bearing guard once a second engine is added. Adds unit tests + an integration test for the non-copilot rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good overall — the security design is solid. One minor validation gap worth fixing, plus a structural observation. Findings🐛 Bugs / Logic Issues
|
Addresses PR review feedback: - Reject a negative/leading-punctuation app-id. `de_string_or_number` stringifies a negative YAML integer (app-id: -7654321 -> "-7654321"), which passed the [A-Za-z0-9._-] charset check because '-' is allowed, yielding a bad JWT `iss` at runtime. validate() now requires the first character to be alphanumeric. Added unit tests. - Extract detection_job_needs_ado_script_bundle() predicate so the Detection job stages the ado-script bundle once, and future detection-only consumers OR into it (mirrors the Agent-job predicate in AdoScriptExtension) rather than adding a second download. - Add BashStep::with_continue_on_error builder so the revoke step is a clean builder chain instead of interrupting it with a direct field mutation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good — well-designed and securely implemented. One documentation bug that will mislead users at authoring time. Findings🐛 Bugs / Logic Issues
✅ What Looks Good
|
…g prompts - engine.md: note the compile-time secrecy advisory and that github-app-token is a hard error on non-copilot engines (previously undocumented behaviors). - create/update/debug authoring prompts: add github-app-token guidance (org-backed Copilot auth) — a create-time engine note, an update scenario, and a debug troubleshooting entry — each linking to docs/engine.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses PR review: the GithubAppTokenConfig struct-level doc comment still described app-id (and private-key) as "ADO pipeline variable names" with an `app-id: GH_APP_ID` example, contradicting the field-level doc and the actual schema. app-id is a literal value (numeric App ID or client ID); only private-key names a secret variable. Fixed the opening sentence and the example (app-id: 1234567). IDE hover / rustdoc now match runtime behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Well-designed feature with sound security architecture — the argv/env split for secret isolation is particularly solid. Two findings worth addressing. Findings🐛 Bugs / Logic Issues
|
Summary
Implements first-class GitHub App-backed Copilot engine auth for #1326 — source-clean, with no VSIX and no ADO service connection. Mirrors gh-aw''s
create-github-app-tokenmodel, adapted to Azure DevOps: the compiler mints an installation token at runtime from a private key stored as an ADO secret.Front matter
Contract (consistent with
GITHUB_TOKEN)The compiler owns variable names; the user owns values:
private-keydefaults to the compiler-owned secretGITHUB_APP_PRIVATE_KEY(set viaado-aw secrets set); optional override names a different secret.app-idis a literal (numeric App ID or alphanumeric client ID) — non-secret per-app config, never indirected through a variable.GITHUB_APP_TOKEN.How it works
github-app-tokenado-script bundle builds an RS256 JWT (node:crypto, no openssl/npm deps) → resolves the installation forowner→POST /app/installations/{id}/access_tokens(optionally repo-scoped) → masked same-jobGITHUB_APP_TOKEN.GITHUB_TOKENfrom$(GITHUB_APP_TOKEN). The token is never provided to SafeOutputs, usersteps:, ManualReview, Teardown, or Conclusion.always()+continueOnError) revoke step (DELETE /installation/token) runs after the Copilot run unlessskip-token-revocation: true.--app-id,--owner,--output-var,--repositories,--api-url) — immune to ADO pipeline-variable shadowing; only the private key rides in masked env.network.allowedentry. Bundle download is driven by a first-classgithub_app_token_activepredicate in the ado-script extension (Agent) and adetection_job_needs_ado_script_bundlepredicate in Detection — one download per job by construction.github-app-tokenis Copilot-engine only — a hard compile error on any otherengine.id(prevents a silent no-op). A non-blocking compile advisory reminds the author to store the private-key variable as a secret.permissions.read/writestay separate; the App token is Copilot-engine-auth only. Copilot capability is granted App/org-side (nopermissions:sub-field).Design notes
GITHUB_TOKEN) avoids a Marketplace extension.main(includes refactor(compile): centralize ado-script bundle env contract #1315) and conformed tosrc/compile/ado_bundle.rs:Bundle::GithubAppTokenregistered withBundleAuth::None(authenticates to GitHub, not ADO REST).Tests / validation
cargo build,cargo test,cargo clippy --all-targets --all-features(0 warnings),bash_lint_tests, ado-scripttypecheck+ full vitest suite. Default (no config) output is byte-identical.Docs
docs/engine.md,docs/ado-script.md,AGENTS.md, and the create/update/debug authoring prompts underprompts/.Closes #1316