Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(supply-chain): add PR-time release-age, install-script, and build-hook checks#2865

Merged
imran-siddique merged 4 commits into
microsoft:mainfrom
jackbatzner:jackbatzner/jb-supply-chain-scans
Jun 11, 2026
Merged

feat(supply-chain): add PR-time release-age, install-script, and build-hook checks#2865
imran-siddique merged 4 commits into
microsoft:mainfrom
jackbatzner:jackbatzner/jb-supply-chain-scans

Conversation

@jackbatzner

Copy link
Copy Markdown
Collaborator

Description

Adds three new supply-chain CI checks plus a shared helpers module to defend the repo (and downstream consumers of its packages) against the most common npm and PyPI supply-chain attack patterns: the event-stream, ua-parser-js, node-ipc, and xz-utils class of incidents.

This extends the existing dependency-confusion-check job in .github/workflows/supply-chain-check.yml with three siblings and a trip-wire:

  1. release-age — fails a PR when it pins a new dep whose exact version was published less than 7 days ago. Most malicious releases are detected and yanked within 72 hours; the 7-day floor lets the ecosystem react before our CI accepts the package.
  2. install-scripts — fails a PR when it adds an npm dep whose registry record advertises preinstall / install / postinstall lifecycle hooks. These run with full shell privileges the moment npm install runs, and are the single most-abused attack surface in npm history.
  3. build-hooks — warns (errors under --strict, which CI uses) when a PR adds a top-level setup.py or build.rs — the Python / Rust equivalents of npm install scripts.
  4. scanner-trip-wire — fails a PR that touches scanner code and dependency manifests in the same diff, blocking the "soften a check + add a hostile dep in one PR" pattern.

Scanners parse manifests structurally (git show base:pathtomllib / json) rather than diffing raw lines, which closes a class of diff-injection and parser-evasion bugs surfaced during a multi-model red-team review (17 findings, all resolved before this PR). Pure stdlib — no new third-party deps, no network egress beyond the three official registries.

Value to the repo and the community

  • Repo: every PR that lands here, including future Dependabot bumps and external contributions, now gets a 7-day cool-off + lifecycle-script + build-hook gate at the cheapest enforcement point (pre-merge). The existing copilot-instructions.md already documents the 7-day rule as policy; this PR is the missing enforcement layer.
  • Downstream: the published Python / npm / Rust / .NET packages from this repo are consumed by other Microsoft teams and external developers. Hardening the inputs hardens every downstream release.
  • Community: the scanners are stdlib-only and MIT-licensed. Any other Microsoft repo (or external project) can lift them wholesale; the architecture (structural parse, fail-closed transients, trip-wire) is documented in code comments for re-use.

Security benefits in concrete terms

Vector Before this PR After
Same-day malicious release pinned in a PR Merges if no obvious flag Fails release-age with timestamp + remediation
Typosquat package (registry 404) Silent install failure later Hard-fails release-age at PR time
npm dep with postinstall (event-stream class) Runs on every CI npm install Fails install-scripts with the exact command quoted
Adding a setup.py / build.rs quietly No signal Visible warning + --strict failure in CI
Hostile PR softening a scanner + adding a dep in one diff Possible Fails scanner-trip-wire
Lockfile lying about hasInstallScript Trusted Registry overrides lockfile; reports LOCKFILE LIED

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Maintenance (dependency updates, CI/CD, refactoring)
  • Security fix

Package(s) Affected

  • agent-os-kernel
  • agent-mesh
  • agent-runtime
  • agent-sre
  • agent-governance
  • docs / root — scripts/ and .github/workflows/supply-chain-check.yml

Checklist

  • My code follows the project style guidelines (ruff check --select E,F,W --ignore E501 clean on all 8 new files)
  • I have added tests that prove my fix/feature works — 175 new unit tests across 4 test files (71 + 53 + 43 + 8)
  • All new and existing tests pass — full pytest scripts/tests/ shows 344/344 pass
  • I have updated documentation as needed — inline module docstrings on all new modules; user-facing failure messages include remediation text; no doc-tree changes needed
  • I have signed the Microsoft CLA

Attribution & Prior Art

  • This contribution does not contain code copied or derived from other projects without attribution
  • Any external projects that inspired this design are credited in code comments or documentation
  • If this PR implements functionality similar to an existing open-source project, I have listed it below

Prior art / related projects:

The 7-day release-age policy and lifecycle-script flagging are well-established defences. Commercial scanners (Socket, Snyk Advisor, Phylum, JFrog Xray) and OpenSSF Scorecard all implement variants. This PR is original code, not a wrapper around any of those — pure stdlib, scoped to PR-time only, intentionally narrow surface so the entire scanner is auditable in one sitting. The threat model is grounded in well-documented public incidents (event-stream 2018, ua-parser-js 2021, node-ipc 2022, xz-utils 2024) referenced in module docstrings.

The repo's copilot-instructions.md already codifies the 7-day rule as repo policy under "Supply Chain Security (Anti-Poisoning)" — this PR is the enforcement implementation of policy that already exists.

AI Assistance

  • I can explain every meaningful change in this PR: what it does, why, and what tradeoffs were considered
  • I have run tests and verification appropriate for this change
  • No part of this PR was autonomously submitted by an AI agent without my review
  • I have not used AI to generate review comments on others' PRs

Authored with Copilot CLI (Claude Opus 4.7). The implementation went through a multi-model red-team review (Claude Opus 4.6 + GPT 5.4 fanned across 6 reviewer dimensions = 12 parallel agents) which produced 17 findings (5 critical, 5 high, 5 medium, 2 process). All 17 are resolved in this commit; the architectural rewrite to structural manifest parsing is a direct response to the critical/high findings about diff-line injection and parser evasion. Tests include explicit regression coverage for each fixed finding.

IP, Patents, and Licensing

  • This contribution does not implement patent-pending or patent-encumbered techniques
  • This contribution does not require an NDA or licensing agreement to understand or use
  • Any AI tools used have terms compatible with the MIT License

Related Issues

None. Defensive hardening; no incident or filed issue triggered this.


Reviewer notes

  • Trip-wire interaction: because this PR adds scanner code, the scanner-trip-wire job will be visible on this PR's CI run but will not fire — the trip-wire fails only when scanner code and dep manifests change together, and this PR touches no manifests.
  • .github/ change: repo policy normally asks for separate PRs for .github/ changes. The workflow file change here is inseparable from the scanner code it invokes (the new jobs reference the new scripts), so they ship together. Workflow changes are line-by-line: 3 new jobs + 1 trip-wire job, all SHA-pinned actions, all persist-credentials: false, all top-level contents: read.
  • Friction trade-off on install-scripts: legitimate native-binary packages (sqlite3, node-canvas, bcrypt) use postinstall to build C extensions and will fail this check by default. That is intentional — if any future PR needs such a dep, a maintainer should land it in a separate, reviewed PR and we can add a narrow allow-list. Prebuilt binaries (prebuild-install) are the preferred modern path.
  • Follow-ups already in flight: sister branches exist for proposals build(deps): Bump scikit-learn from 1.3.2 to 1.5.0 in /packages/agent-os/modules/caas #4 (PR-time SBOM diff), build(deps-dev): Bump underscore from 1.13.7 to 1.13.8 in /packages/agent-os/extensions/cursor #5 (lockfile hash integrity), and build(deps-dev): Bump underscore from 1.13.7 to 1.13.8 in /packages/agent-os/extensions/vscode #6 (OpenSSF Scorecard on new deps). Those land as separate PRs after this one.

…d-hook checks

Adds three new supply-chain CI checks to defend against common npm and PyPI

supply-chain attack patterns (event-stream, ua-parser-js, node-ipc, xz-utils class).

* release-age: fails PRs that pin a dep version published <7 days ago

* install-scripts: fails PRs that add an npm dep with preinstall/install/postinstall

* build-hooks: warns on new top-level setup.py or build.rs (strict in CI)

* scanner-trip-wire: fails PRs that edit both scanner code and dep manifests

Scanners parse manifests structurally (git show base:path -> tomllib/json)

rather than diffing raw lines, closing a class of injection and parser-evasion

bugs surfaced during a multi-model red-team review. Pure stdlib, no new deps.

Tests: 175 new (71 + 53 + 43 + 8); 344/344 in scripts/tests/ pass.

Co-authored-by: Copilot <[email protected]>
Signed-off-by: Jack Batzner <[email protected]>
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR Review Summary

Check Status Details
🔍 Code Review ⚠️ Missing No current-run comment
🛡️ Security Scan ⚠️ Missing No current-run comment
🔄 Breaking Changes ⚠️ Missing No current-run comment
📝 Docs Sync ⚠️ Missing No current-run comment
🧪 Test Coverage ⚠️ Missing No current-run comment

Verdict: ⚠️ AI review incomplete; ready for human review

AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims.

Adds 21 legitimate vocabulary terms used by the new supply-chain scanners to the cspell dictionary (pathspec, basenames, packument, typosquats, Birsan, rustdecimal, esbuild, deasync, etc.) and syncs the workflow generator manifest with actions/checkout v6.0.3 (which Dependabot PR microsoft#2843 already applied to .github/workflows but not to .github/ci/actions.toml). The generator now produces output matching the committed YAML, so the 'Check generated workflows' and 'inline-script-tests' jobs pass.

Co-authored-by: Copilot <[email protected]>
Signed-off-by: Jack Batzner <[email protected]>
@jackbatzner

Copy link
Copy Markdown
Collaborator Author

CI follow-up (commit bc198bb7):

  1. .github/ci/actions.toml — Dependabot PR chore(deps): bump actions/checkout from 4.2.2 to 6.0.3 #2843 bumped actions/checkout from v4.2.2 to v6.0.3 in .github/workflows/*.yml but didn't update the generator manifest. That left main in a drifted state where scripts/ci/generate_workflows.py --check fails on every PR. I bumped the manifest SHA so generator output matches the committed YAML again. This unblocks both Check generated workflows and inline-script-tests (the 2 failures there were test_committed_yaml_matches_manifest and test_check_mode_passes_on_committed_tree — both driven by the same drift).
  2. .cspell-repo-terms.txt — added 21 legitimate vocabulary terms used by the new scanners (pathspec, basenames, packument, typosquats, Birsan — Alex Birsan, who coined dependency confusion in 2021 — rustdecimal/esbuild/deasync from documented supply-chain incidents, etc.).

Happy to split the actions.toml fix into its own PR if a maintainer prefers; flagged here for transparency since it touches a file outside the supply-chain scanner scope.

cc @imran-siddique

@imran-siddique imran-siddique left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good implementation. The critical design decisions are right: structural manifest parsing instead of diff-line regex, fail-closed on transient registry failures, lockfile hint treated as informational only (never as a skip signal), and nested entries correctly unwrapped.\n\nOne minor issue in : detects when quoting changed something unexpected, logs a comment saying "something is wrong with the input" -- and then es instead of raising. The upstream / checks make this a defense-in-depth gap rather than a live bypass, but it should raise to be consistent with the fail-closed contract documented throughout.\n\nThe trip-wire design is correct and the self-referential note (this PR adds scanner code but no manifests, so trip-wire won't fire) is accurate.\n\nCI is green. Approve pending the silent-pass fix, which can land as a follow-up given the upstream guards already cover it.

imran-siddique
imran-siddique approved these changes Jun 8, 2026
imran-siddique
imran-siddique previously approved these changes Jun 8, 2026
…-chain-scans

Signed-off-by: Jack Batzner <[email protected]>

# Conflicts:
#	.cspell-repo-terms.txt
@imran-siddique

Copy link
Copy Markdown
Collaborator

The core design is sound. One structural note: the actions.toml fix is bundled into this PR but should be its own PR per the policy on separate .github/ changes — it's harder to revert independently if needed. Please split it out. @AbuOmar for maintainer review once that's addressed.

imran-siddique
imran-siddique previously approved these changes Jun 10, 2026

@imran-siddique imran-siddique left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Well-engineered PR. The three checks are correctly hardened — fail-closed on registry failures, size-bounded HTTP reads, strict regexes, deadline budgets, no shell interpolation of user-controlled data, and least-privilege workflow permissions. 175 tests with explicit red-team regression labels is excellent. Approving with two optional suggestions.

Optional (worth fixing before merge)

  1. PYTHONPATH not set in workflow (.github/workflows/supply-chain-check.yml, release-age and install-scripts job steps): The scripts do import _supply_chain_common as common which works because the runner cwd is the repo root — but this is an implicit assumption. If a checkout step ever uses a path: key, the import silently fails. Add env: PYTHONPATH: ${{ github.workspace }}/scripts to those steps (the test files already do this via sys.path manipulation, which confirms the risk is known).

  2. SAFE_NAME_RE allows / in non-scoped names (scripts/_supply_chain_common.py, safe_url_path): The regex \A@?[A-Za-z0-9][A-Za-z0-9._/-]*\Z accepts some/path as a valid package name. The downstream safe_url_path percent-encodes the / to %2F, so there's no injection risk, but the false acceptance would produce a confusing HARD FAIL: 404 instead of REJECTED: unsafe name syntax. Consider narrowing to disallow / in non-scoped names.

Merge blocker (mechanical): Resolve the conflict — the PR is DIRTY against main.

Resolve conflicts in .cspell-repo-terms.txt (kept main's structure, added
only the PR's new terms in sorted position) and
.github/workflows/supply-chain-check.yml (kept both main's lockfile-integrity
job and the PR's release-age, install-scripts, and build-hooks jobs).

Signed-off-by: Imran Siddique <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scripts/ci/cd size/XL Extra large PR (500+ lines) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants