Skip to content

fix(compile): keep package-feed credentials out of the AWF sandbox - #2310

Open
jamesadevine wants to merge 1 commit into
mainfrom
fix/runtime-feed-credential-isolation
Open

jamesadevine wants to merge 1 commit into
mainfrom
fix/runtime-feed-credential-isolation

Conversation

@jamesadevine

Copy link
Copy Markdown
Collaborator

Summary

Package-feed credentials currently reach the AWF agent sandbox. Azure
Pipelines' NuGetAuthenticate, PipAuthenticate, and CargoAuthenticate
tasks must export their credentials as non-secret job variables so their
client tools can read them. These variables include VSS_NUGET_ACCESSTOKEN
and a https://build:<token>@… value in PIP_EXTRA_INDEX_URL.
npmAuthenticate appends _authToken=<token> to its working .npmrc. Azure
Pipelines injects non-secret variables into every later step's environment,
and both AWF runs use --env-all. That exposed the job's build-identity token
to the agent in these cases:

  • Every supply-chain.feed pipeline: NuGetAuthenticate@1 ran in the
    Agent and Detection jobs, including the shared ado-script download path.
    DownloadPackage@1 does not need it; it authenticates itself through
    SYSTEMVSSCONNECTION.
  • runtimes.python, runtimes.node, and runtimes.dotnet with
    feed-url or config:
    PipAuthenticate@1, npmAuthenticate@0 writing to
    the workspace .npmrc, and NuGetAuthenticate@1 all ran before AWF in the
    Agent job.
  • Operator steps: or safe-outputs.threat-detection.steps using any
    *Authenticate package task:
    these steps also ran before AWF.

This PR:

  • removes NuGetAuthenticate@1 from the Agent and Detection jobs and from the
    shared ado-script feed download. Non-AWF jobs, such as SafeOutputs and custom
    safe-output jobs, still emit it;
  • removes PipAuthenticate@1, npmAuthenticate@0 with its ensure-.npmrc
    step, and NuGetAuthenticate@1 from the runtime extensions. feed-url still
    selects the source (PIP_INDEX_URL, UV_DEFAULT_INDEX,
    NPM_CONFIG_REGISTRY, or the generated nuget.config), and the compiler
    warns that the agent has no feed credential;
  • rejects NuGetAuthenticate, npmAuthenticate, PipAuthenticate,
    TwineAuthenticate, CargoAuthenticate, and MavenAuthenticate in
    steps: and safe-outputs.threat-detection.steps; setup:, post-steps:,
    and teardown: are unaffected;
  • adds --exclude-env for VSS_NUGET_ACCESSTOKEN,
    VSS_NUGET_EXTERNAL_FEED_ENDPOINTS, PIP_EXTRA_INDEX_URL, and
    CARGO_REGISTRY_TOKEN to both AWF runs as defense in depth.

Behaviour change: authenticated private-feed restores from inside the
agent no longer work. They previously worked only by giving the agent the
credential. Credential-isolated feed access through the ado-proxy is planned
as a follow-up.

Evidence from the task sources:

  • NuGetAuthenticateV1: credentialProviderUtils.ts:173-174,210
    (setVariable(..., false)).
  • PipAuthenticateV1: pipauthenticatemain.ts:94,100 and
    utilities.ts:33-34.
  • NpmAuthenticateV0: npmauthutils.ts:209.
  • CargoAuthenticateV0: cargoauthenticatemain.ts:179-182.
  • DownloadPackageV1: connections.ts:8-24 and
    locationUtilities.ts:122-128.

Test plan

  • cargo build: passed.
  • cargo test --no-fail-fast: passed, with 3721 passed, 0 failed, and 2
    ignored. New coverage:
    • test_supply_chain_feed_never_authenticates_in_awf_jobs verifies that
      Agent and Detection have no NuGetAuthenticate@1 and exclude
      VSS_NUGET_ACCESSTOKEN, while SafeOutputs keeps the task.
    • package_authenticate_tasks_are_rejected_only_before_awf verifies the
      operator-step guard.
    • package_credential_keys_are_always_excluded_from_both_awf_runs verifies
      the exclusions.
    • Runtime extension and compiler tests verify that feed-url emits no
      authenticate task or workspace .npmrc.
  • cargo clippy --all-targets: passed with no diagnostics.
  • ENFORCE_BASH_LINT=1 cargo test --bin ado-aw compile::shell: passed.
  • ENFORCE_BASH_LINT=1 cargo test --test bash_lint_tests: passed.
  • git diff --check: only CR-at-EOL is reported on three README.md runtime
    table rows; they preserve the CRLF endings that section already uses.

Azure Pipelines package-authenticate tasks export their credential as non-secret job variables (or an agent-visible .npmrc), which AWF --env-all forwarded to the agent. Stop emitting them in AWF jobs, reject them in pre-AWF operator steps, and exclude the static credential keys.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7b0ab0d4-23f4-4a89-90ea-3c40758ea550
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@jamesadevine

Copy link
Copy Markdown
Collaborator Author

/review

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

✅ Rust Code Quality Reviewer completed the Rust code quality review.

🦀 Rust code quality review by Rust Code Quality Reviewer

@github-actions github-actions Bot mentioned this pull request Oct 8, 2026

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

Security review: no regressions found

This diff is security-positive — it closes a real credential-leak class rather than weakening anything. Summary of what I checked:

  • awf_exclude_keys now always includes PACKAGE_CREDENTIAL_ENV_KEYS (VSS_NUGET_ACCESSTOKEN, VSS_NUGET_EXTERNAL_FEED_ENDPOINTS, PIP_EXTRA_INDEX_URL, CARGO_REGISTRY_TOKEN) and is wired into both the Agent and Detection AWF invocations (agentic_pipeline.rs:351,354), matching the new --exclude-env defense-in-depth claim.
  • NuGetAuthenticate@1 is removed from the Agent/Detection jobs (which run AWF with --env-all) and correctly retained only in non-AWF jobs (SafeOutputs/Conclusion/custom jobs), verified by the new test_supply_chain_feed_never_authenticates_in_awf_jobs test which explicitly asserts both outcomes.
  • npmAuthenticate@0/PipAuthenticate@1/the ensure-.npmrc/nuget.config-auth steps are removed from the Python/Node/.NET runtime extensions, replaced with feed-url/config emitting only source-selection (no-credential) config plus an explicit compile warning (unauthenticated_feed_warning).
  • New validate_no_package_authenticate_before_awf closes the same hole for operator-authored steps: and safe-outputs.threat-detection.steps, correctly scoped to only the pre-AWF windows (setup:/post-steps:/teardown: remain unaffected, confirmed by the paired test).
  • Docs (docs/runtimes.md, docs/ado-proxy-design.md, docs/supply-chain.md) accurately reflect the new behavior and the consequences for authenticated feeds.

I traced every removed/added call site of feed_auth_step, nuget_authenticate_step, and install_and_download_steps_typed across the Agent/Detection/SafeOutputs/Conclusion job builders — behavior is consistent with the stated intent in each case. cargo build is clean. No further action needed from me.

🔒 Security review by PR Security Reviewer · auto · 68.3 AIC · ⌖ 2.02 AIC · ⊞ 11.4K
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.

Reviewed the package-feed credential isolation changes against the compiler-contract checklist:

  • Front-matter grammar — no field renames/removals in types.rs; doc-comment-only edits plus docs/front-matter.md / docs/runtimes.md updated in lockstep. No codemod needed.
  • Typed IR / tasks — nuget_authenticate_step visibility narrowed pub(crate) -> private with its only remaining caller (feed_auth_step) in the same module; no external callers broken (verified via grep).
  • Shell scripts — the removed ENSURE_NPMRC shell_script! registration is cleanly deleted along with its only caller and its bash_lint_tests.rs required-display-name entry; no orphaned registry entries.
  • New validator (validate_no_package_authenticate_before_awf) is wired into validate_pipeline_front_matter and covered by a unit test exercising all 4 listed tasks across steps, safe-outputs.threat-detection.steps, and the three unaffected locations (setup, post-steps, teardown).
  • Docs — docs/ado-proxy-design.md, docs/runtimes.md (new Package-feed credentials section), docs/supply-chain.md, and docs/front-matter.md all updated consistently with the code change.
  • Tests — ran the full targeted suite plus cargo test --bin ado-aw (3408 passed), cargo test --test compiler_tests (244 passed), and cargo test --test bash_lint_tests (2 passed). All green.

No drift between the Rust source, generated behavior, and documentation found. Nothing merge-blocking from a compiler-contract perspective.

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • releaseassets.githubusercontent.com
  • spsprodeus21.vssps.visualstudio.com

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

network:
  allowed:
    - defaults
    - "releaseassets.githubusercontent.com"
    - "spsprodeus21.vssps.visualstudio.com"

See Network Configuration for more information.

🏗️ Compiler contract review by Compiler Contract Reviewer · auto · 100.5 AIC · ⌖ 2.54 AIC · ⊞ 12.5K
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.

Rust review: no merge-blocking issues found

Note: the rust-critic sub-agent failed to start (model access error: gpt-5.4-mini not accessible via /chat/completions), so this review reflects my own pass only.

Scope checked: src/compile/agentic_pipeline.rs, src/compile/common.rs, src/compile/extensions/ado_script.rs, src/compile/types.rs, src/runtimes/{dotnet,node,python}/{mod,extension}.rs, src/runtimes/mod.rs, tests/bash_lint_tests.rs, tests/compiler_tests.rs.

Verified:

  • cargo check --bin ado-aw — clean, no warnings (confirms no dead code/unused imports after nuget_authenticate_step visibility tightened to private and the npm/pip auth-step helpers were deleted).
  • cargo test --bin ado-aw runtimes:: — 28/28 pass.
  • New validator tests (package_authenticate_tasks_are_rejected_only_before_awf, package_credential_keys_are_always_excluded_from_both_awf_runs) pass.

Quality observations (none rise to merge-blocking):

  • validate_no_package_authenticate_before_awf and PACKAGE_CREDENTIAL_ENV_KEYS/PACKAGE_AUTHENTICATE_TASKS constants are well-documented with clear rationale comments explaining the credential-leak mechanism.
  • Task-name matching (package_authenticate_task) correctly strips the @version suffix and does case-insensitive comparison — handles the npmauthenticate@0 lowercase test case correctly.
  • The three runtime extensions (dotnet/node/python) apply the same warning pattern consistently via the shared unauthenticated_feed_warning helper — good DRY.
  • Test coverage is thorough: covers the allowed locations (setup, post-steps, teardown) staying unaffected, and the rejected locations (steps, threat-detection.steps) for every listed task.

No panics, lossy casts, ordering/determinism issues, or weakened error handling introduced by this diff. The change is a surgical, well-tested removal of credential-leaking tasks from AWF-sandboxed jobs.

🦀 Rust code quality review by Rust Code Quality Reviewer · auto · 107.2 AIC · ⌖ 13.6 AIC · ⊞ 11.2K
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 coverage for this credential-isolation fix is strong: the common.rs validator has proper positive (setup/post-steps/teardown allowed) and negative (rejected in steps/threat-detection.steps) paths, the awf_exclude_keys tests assert exact key sets rather than just emptiness/non-emptiness, and the runtime extension tests (node/python/dotnet) were correctly flipped from "task present" to "task absent + warning present" assertions rather than just deleted. The new test_supply_chain_feed_never_authenticates_in_awf_jobs integration test also confirms the asymmetry is preserved: Agent/Detection never get NuGetAuthenticate@1 while SafeOutputs still does, and the existing per-job hoisting test (auth_count vs download_count) still exercises the kept code path. One minor gap noted inline.

🧪 Test quality analysis by Test Quality Sentinel · auto · 113.3 AIC · ⌖ 2.21 AIC · ⊞ 11K
Comment /review to run again

This branch has not been deployed

No deployments
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.

1 participant