feat(marketplace): add audit command to flag deps that bypass marketplace pinning#881
Conversation
danielmeppiel
left a comment
There was a problem hiding this comment.
Strategic context
Thanks for this work, @edenfunf. A dependency-bypass auditor is exactly the kind of marquee surface APM's Secure-by-default story needs, and the supply-chain rigor in this PR (see "What's already great" below) is well above the bar we usually see for a first contribution. We very much want to land this -- but the branch has drifted hard against main since you opened it, and a clean rebase isn't mechanical anymore. Below are the three blockers, with concrete options for each.
Blockers (ordered)
[x] 1. Naming collision: apm marketplace doctor already exists on main
Since refactor PR #1024, main ships an apm marketplace doctor command at src/apm_cli/commands/marketplace/doctor.py that runs environment diagnostics (git binary, network reachability, auth, marketplace config readiness). Your doctor is a dependency-bypass auditor -- different inputs, different outputs, different mental model. They cannot coexist under the same name.
Resolution options (pick one -- see "Naming proposals" below for our recommendation).
[x] 2. Target file src/apm_cli/commands/marketplace.py was deleted on main
PR #1024 split that single file into a marketplace/ package: __init__.py, check.py, doctor.py, init.py, migrate.py, outdated.py, publish.py, validate.py, plugin/. Your PR adds 112 lines to the now-deleted marketplace.py, so a rebase produces CONFLICT (modify/delete).
Resolution: re-port your command registration into the new package layout. Concretely, after picking a name (say audit), add src/apm_cli/commands/marketplace/audit.py and wire it from src/apm_cli/commands/marketplace/__init__.py next to the existing check, doctor, outdated, etc. registrations.
[!] 3. src/apm_cli/marketplace/client.py has evolved independently
Your PR refactors _try_proxy_fetch -> _try_proxy_fetch_raw and adds fetch_raw. Main has independently changed the same _try_proxy_fetch (modern dict | None return type, cfg.scheme / headers wiring). The conflict is resolvable but non-trivial -- please reconcile against current main rather than force-applying your version, so the proxy fetch path keeps the upstream improvements.
Naming proposals (you pick)
In rough order of our preference:
apm marketplace audit-- our recommendation. Cleanest semantic split from environmentdoctor, easy to document, no breaking change for existing users. Short, memorable, signals "supply-chain audit".- Fold into
apm marketplace check --strict-pinning-- attractive if you'd rather not introduce a new top-level subcommand. Trade-off:checkis currently lockfile drift / integrity oriented, so the flag would need a clear scope. - Rename existing env-diagnostic to
apm marketplace env-doctor, keepdoctorfor your auditor -- works semantically, but it's a breaking change to a shipped command and would need a deprecation shim plus a CHANGELOGBREAKINGentry.
Maintainers are happy with option 1. If you'd prefer 2 or 3, flag it in your next push and we'll align.
Doc requirements (repo doc-sync rule)
Any new CLI command must update the following in the same PR:
CHANGELOG.md-- new### Addedentry under[Unreleased], following the Keep a Changelog format already used in the file.docs/src/content/docs/reference/cli.md(or the closest existing page underdocs/src/content/docs/) -- one short section: synopsis, flags, exit codes, a one-line example.packages/apm-guide/.apm/skills/apm-usage/commands.md-- add the new command alongside the otherapm marketplace ...entries so the in-product guide stays in sync.
If option 3 above is chosen, also add a ### Changed (BREAKING) CHANGELOG entry for the doctor rename and update any docs that reference the existing marketplace doctor.
What's already great
This is a serious piece of work and we don't want any of it lost in the rebase:
classify_dependency-- clean separation between marketplace-pinned, bypassed, and ambiguous cases. Exactly the right primitive for this surface.- Dict-form bypass detection -- catching the
{name: ..., source: ...}long-form that side-steps marketplace pinning is the subtle case most reviewers would have missed. FetchStatusenum for fault isolation -- distinguishing "marketplace says no" from "network failed" from "auth missing" is the right call; it keeps the auditor honest under partial outages and makes exit-code semantics tractable.- Path-traversal guards -- aligned with the repo's
validate_path_segments/ensure_path_withinrule. Good instinct. - 592 lines of unit tests for
doctor.py-- coverage is genuinely thorough.
None of this needs to change. The blockers are all about where the code lives and what it's called, not what it does.
Next step
- Rebase onto current
main. - Pick a name (we recommend
audit). - Re-port the command into the new
src/apm_cli/commands/marketplace/package layout. - Reconcile
marketplace/client.pyagainst main's_try_proxy_fetchrather than overwriting it. - Add the three doc updates listed above.
- Re-request review.
If the rebase is painful (it will be), say the word in a comment and a maintainer will pair on it or push a rebase branch you can pull from. We'd rather help you across the line than see this stall.
Thanks again for the contribution.
8f1c9d4 to
ec91c42
Compare
|
@danielmeppiel thanks for the thorough review. I have rebased onto current How each blocker was resolved1. Naming collision ( 2. Target file deletion -- re-ported the command into the new 3. Doc sync (per repo rule)
TestsTotal 56 new tests, all passing alongside the existing suite (6982 passed):
Other small improvements made during the rebase
|
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a new apm marketplace audit command to detect transitive dependencies.apm entries that bypass marketplace pinning by fetching each plugin’s apm.yml at its pinned ref and classifying dependency shapes.
Changes:
- Refactors marketplace client proxy fetching to expose a reusable
fetch_raw()primitive for arbitrary files at a ref. - Implements marketplace audit core logic + Click CLI command (
apm marketplace audit NAME, with--strict/-v). - Adds unit + CLI integration tests and updates user-facing docs + changelog.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/marketplace/test_marketplace_client.py | Adds focused tests for the new fetch_raw() error contract + proxy-only behavior. |
| tests/unit/marketplace/test_marketplace_audit.py | Adds extensive test coverage for dependency classification, dep normalization, plugin coord resolution, fetch error messaging, and CLI --strict behavior. |
| src/apm_cli/marketplace/client.py | Factors byte-level proxy fetch and introduces fetch_raw() for raw file retrieval with shared auth/proxy behavior. |
| src/apm_cli/marketplace/audit.py | Implements audit core: classification, dep collection/normalization, plugin GitHub coord resolution, per-plugin fetch/parse, and aggregation. |
| src/apm_cli/commands/marketplace/audit.py | Adds the apm marketplace audit command with summary output and CI-friendly --strict exit behavior. |
| src/apm_cli/commands/marketplace/init.py | Registers the new audit subcommand in the marketplace command group exports. |
| packages/apm-guide/.apm/skills/apm-usage/commands.md | Documents apm marketplace audit NAME in the commands table. |
| docs/src/content/docs/reference/cli-commands.md | Adds detailed CLI reference documentation for apm marketplace audit. |
| CHANGELOG.md | Announces the new command and its behavior/exit semantics. |
13f7e29 to
e7b603d
Compare
|
Pushed Addressed in code(items 2-4) (item 7) Also caught during the rebase
Not changed(items 5-6) (item 1) The "doctor"/"audit" + Status
|
Reconcile the marketplace audit command against main's evolved fetch layer: - client.py: rebuild fetch_raw on top of main's modern proxy/auth path. Keep main's _try_proxy_fetch and host-aware _github_contents_url; add _try_proxy_fetch_raw (byte-level proxy primitive) and _github_contents_url_generic for the plugin apm.yml raw-bytes fetch. _try_proxy_fetch now delegates to _try_proxy_fetch_raw to stay DRY while preserving the upstream scheme/header wiring. - Register 'audit' in the new commands/marketplace/ package layout. - Re-home the audit CLI reference from the deleted cli-commands.md into cli/marketplace.md; keep commands.md in sync. - Move the microsoft#881 CHANGELOG entry back under [Unreleased].
…lace pinning
A marketplace author pinning a package at a specific SHA still leaves
a supply-chain gap: if that package's own apm.yml declares
dependencies.apm using direct repo paths (e.g. owner/repo/path),
those transitive deps are resolved via git clone and track HEAD --
the marketplace's version pinning does not flow through them.
Add apm marketplace audit <name>:
- Fetches each plugin's own apm.yml at its pinned ref and warns when
a dependencies.apm entry would be resolved outside the marketplace
catalogue.
Classification:
- Bypasses (warn): bare owner/repo, owner/repo/subpath, https:// /
ssh:// git URLs, and { git: URL, ... } object-form entries.
- Clean: name@marketplace refs; local paths (./x, /abs, ../x).
- Skipped: plugin has no apm.yml at the pinned ref, or source type
is not an addressable github manifest.
- Unverifiable errors: fetch failure or malformed YAML (per-plugin
isolation -- one bad plugin does not abort the run).
Behavior:
- Default run is informational and exits 0. Suppresses the per-plugin
section header on an all-clean run so the summary is not preceded by
an empty section.
- --strict exits non-zero on any bypass warning or unverifiable
plugin, for use in CI.
- --verbose surfaces clean plugins and skipped reasons inline; on
errors, also prints the captured traceback for debugging.
Implementation notes:
- Lives at src/apm_cli/commands/marketplace/audit.py (new package
layout from microsoft#1024) and src/apm_cli/marketplace/audit.py (domain
logic). Wired into MarketplaceGroup as an Authoring command.
- Coexists with the existing apm marketplace doctor (environment
diagnostics) -- different mental model, different subcommand.
- Aligns with the existing apm audit (content integrity scan) under
the project's "audit = security/integrity" naming family.
- fetch_raw() is added as a new public primitive in marketplace/client.py
alongside the new _try_proxy_fetch_raw() helper. _try_proxy_fetch
and _fetch_file are reconciled with main: their public contracts
are preserved (returns dict | None, raises MarketplaceFetchError);
internally, _try_proxy_fetch now delegates to _try_proxy_fetch_raw
to keep proxy I/O DRY between marketplace.json and arbitrary-file
callers. No existing call site changes.
- fetch_raw() raises the neutral MarketplaceError base so audit can
surface its own per-plugin context instead of inheriting the
MarketplaceFetchError "run apm marketplace update" retry hint,
which is wrong at plugin granularity.
- Object-style dep entries ({ git: ..., path: ..., ref: ... } -- the
same shape DependencyReference.parse_from_dict accepts) are
normalized to strings before classification so they are not
silently dropped.
- Path traversal in the plugin source path field is rejected via the
existing validate_path_segments.
Tests (56 new):
- 53 in tests/unit/marketplace/test_marketplace_audit.py covering the
classifier, dep normalisation including dict-form, plugin coord
resolution, fetch error-message regression, and the CLI command
(default / --strict on bypass / --strict on unverifiable plugin /
unverifiable without strict / clean exits / --verbose output).
- 3 in tests/unit/marketplace/test_marketplace_client.py::TestFetchRaw
pinning the public contract of fetch_raw: raises neutral
MarketplaceError (not MarketplaceFetchError), short-circuits on
proxy hit, and respects PROXY_REGISTRY_ONLY=1 to keep direct GitHub
fetches off.
Docs:
- CHANGELOG.md: Added entry under [Unreleased].
- docs/src/content/docs/reference/cli-commands.md: full command
reference (synopsis, flags, classification, exit codes, examples,
note on bypassing the 1h marketplace.json cache).
- packages/apm-guide/.apm/skills/apm-usage/commands.md: in-product
guide entry alongside other marketplace authoring commands.
Fixes microsoft#847
CodeQL py/incomplete-url-substring-sanitization (high) flagged the raw `assert "gitlab.com" in msg` in test_non_github_host_rejected_before_request. Switch to the existing _quoted_hosts() helper to match the parallel _fetch_file host-guard test on line 515, satisfying the repo's test-conventions rule that URL host assertions parse via urllib.parse rather than substring matching.
Polish marketplace audit docs, help text, URL construction, and terminal output after the review-panel pass. This keeps the rebased PR scoped to the marketplace audit command while removing stale docs references and hardening ref query construction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
69c5c8a to
06b94a3
Compare
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python architect | 0 | 0 | 0 | Domain/CLI separation is clean after the fetch_raw integration. |
| CLI logging expert | 0 | 0 | 0 | Summary output and crash hint were folded into CommandLogger style. |
| DevX UX expert | 0 | 0 | 0 | Command help and synopsis now better explain the audit workflow. |
| Supply chain security expert | 0 | 0 | 0 | Ref query construction is encoded and proxy/auth boundaries remain fail-closed. |
| OSS growth hacker | 0 | 0 | 0 | Changelog and docs now lead with the user value and working reference link. |
| Auth expert | 0 | 0 | 0 | Host classification and token forwarding remain correctly scoped. |
| Doc writer | 0 | 0 | 0 | Docs are on the canonical reference/cli/marketplace.md page. |
| Test coverage expert | 0 | 0 | 0 | Targeted marketplace audit/client tests passed: 97 tests. |
B = highest-signal findings, R = recommended, N = nits. Counts are signal strength, not gates. The maintainer ships.
Recommendation
Ship after maintainer review. The prior merge conflict is resolved, the branch is current with origin/main, lint and targeted tests pass locally, and GitHub reports the available check successful on the pushed head.
Reservations carried from strategic-alignment
- PR was CONFLICTING/DIRTY and required rebase onto latest
origin/main-- addressed by rebasing and force-with-lease pushing head06b94a3. - Prior CHANGES_REQUESTED review appeared addressed; re-run the apm-review-panel to confirm -- addressed by this advisory pass and folded follow-ups.
- Keep the change scoped to the marketplace audit feature -- addressed; all folds were within audit/docs/client surfaces.
Folded in this run
- (panel) Rebased onto current
origin/mainand resolved the docs/client command conflicts -- resolved inba2f4f9. - (panel) Replaced stale CLI docs path in the PR body and docs with canonical
reference/cli/marketplace.mdreferences -- resolved in06b94a3. - (panel) Added
apm marketplace auditdiscoverability in the marketplace CLI reference and cross-linked fromapm audit-- resolved in06b94a3. - (panel) Simplified the changelog entry to lead with the user benefit -- resolved in
06b94a3. - (panel) Made the Click help string and docs synopsis clearer for
--verboseusers -- resolved in06b94a3. - (panel) Routed the summary and unexpected-error hint through CommandLogger-style output -- resolved in
06b94a3. - (panel) URL-encoded GitHub Contents API refs in the existing marketplace fetch URL and new raw fetch URL -- resolved in
06b94a3. - (panel) Corrected the marketplace audit help link to the existing CLI reference anchor -- resolved in
06b94a3.
Copilot signals reviewed
- Copilot produced a summary review but no inline comments requiring classification in this run.
Lint contract
uv run --extra dev ruff check src/ tests/ and
uv run --extra dev ruff format --check src/ tests/ both silent.
CI
gh pr checks 881 --repo microsoft/apm --watch reported all checks successful: license/cla succeeded (0 failing, 0 pending) after 0 CI fix iteration(s).
Mergeability status
Captured from gh pr view 881 --json mergeable,mergeStateStatus,statusCheckRollup immediately after the last push of this run. The orchestrator aggregates the same fields across every shepherded PR into the saga-end mergeability table.
| PR | head SHA | CEO stance | iters | folds | defers | Copilot rounds | CI | mergeable | mergeStateStatus | notes |
|---|---|---|---|---|---|---|---|---|---|---|
| #881 | 06b94a3 |
ship_now | 1 | 8 | 0 | 1 | green | MERGEABLE | BLOCKED | pending maintainer review |
Convergence
1 outer iteration(s); 1 Copilot round(s). Final panel verdict: ship_now.
Ready for maintainer review.
Full per-persona findings
All active panel findings were folded or judged already addressed in the final pushed head. No deferred follow-ups remain.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
The custom MarketplaceGroup.format_commands force-listed every name in _authoring_commands, ignoring Click's hidden=True. The deprecated 'doctor' shim (hidden=True) therefore still appeared in 'apm marketplace --help', failing test_marketplace_doctor_hidden_from_help on main. Skip hidden commands in the formatter so the shim stays invokable but no longer lists. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Description
This PR adds
apm marketplace audit <name>to detect plugins whose transitivedependencies.apmbypass marketplace pinning. It fetches each plugin'sapm.ymlat the pinned ref, classifies dependency shapes, and reports direct repo paths, git URLs, and{ git: ... }entries that would resolve outside the marketplace catalogue.Behavior
--strictexits non-zero when bypass warnings or unverifiable plugins are found, so marketplace owners can use it in CI.--verboseshows clean plugins, skipped reasons, and tracebacks for unexpected errors.Implementation notes
src/apm_cli/commands/marketplace/audit.py.src/apm_cli/marketplace/audit.py.src/apm_cli/marketplace/client.pyaddsfetch_raw()and_try_proxy_fetch_raw()while keepingfetch_marketplace()semantics intact.docs/src/content/docs/reference/cli/marketplace.md,docs/src/content/docs/reference/cli/audit.md,packages/apm-guide/.apm/skills/apm-usage/commands.md, andCHANGELOG.md.Review feedback addressed
audit, leavingapm marketplace doctoras environment diagnostics.origin/mainand resolved the CLI docs split (reference/cli/marketplace.mdis the canonical page).Testing
uv run --extra dev pytest tests/unit/marketplace/test_marketplace_audit.py tests/unit/marketplace/test_marketplace_client.py -q-> 97 passed.uv run --extra dev ruff check src/ tests/-> passed.uv run --extra dev ruff format --check src/ tests/-> passed.Fixes #847