Skip to content

[BUG] Cross-repo bare repo on *.ghe.com marketplace silently resolves at github.com on validation success (dependency-confusion vector) #1326

@edenfunf

Description

@edenfunf

Context

Surfaced by the PR review panel for #1319 (supply-chain-security finding). PR #1319 fixes the visible failure path of #1305 -- when a cross-repo bare repo on a *.ghe.com marketplace fails validation, the install surfaces an actionable host-qualify hint. The panel correctly noted that #1319 deliberately does not address the silent success path of the same syntactic ambiguity.

The gap

The two intents that share the bare cross-repo syntax are:

  • Intentional cross-host: marketplace on corp.ghe.com, repo: opensource-org/awesome-tool -- author wants the github.com open-source dep.
  • Misconfigured same-host: marketplace on corp.ghe.com, repo: platform-team/shared-tool -- author wants the corp.ghe.com enterprise dep.

Resolver-level disambiguation is impossible. PR #1319 covers the case where the misconfigured intent's resolution fails validation (because the same owner/repo is not on github.com), but does not cover the case where an attacker pre-stages platform-team/shared-tool on github.com with malicious content:

  1. Operator publishes a *.ghe.com marketplace with repo: platform-team/shared-tool intending the enterprise repo.
  2. Attacker registers platform-team/shared-tool on github.com first (the enterprise org's namespace is observable from public marketplace.json files in many setups).
  3. apm install shared-tool@my-marketplace resolves canonical to bare platform-team/shared-tool/plugins/shared; DependencyReference.parse defaults host to github.com; _validate_package_exists returns True against the attacker-staged repo.
  4. Install succeeds, lockfile records https://github.com/platform-team/shared-tool as the origin, attacker content executes under the operator's APM context.
  5. The CrossRepoMisconfigRisk sentinel fix: hint to host-qualify cross-repo on *.ghe.com (closes #1305) #1319 attached is consumed only on validation failure; the success path never reaches the hint emission branch.

This is a dependency-confusion-class vector (cf. the npm internal-package-name confusion attacks). Mitigations span resolver + install layers and cannot fit inside #1305's scope without changing the resolver routing PR #1292 deliberately preserved.

Why this is not part of #1319

The #1305 issue body framed the problem as "the misconfigured case silently 401s with no actionable hint" -- a diagnostic-surface concern, not a security-boundary concern. The panel's supply-chain-security review of #1319 explicitly classified this finding as "out of scope for a PR whose stated goal is 'surface actionable hint at the failure boundary'". The PR's review recommendation was "ship after resolving the logger.warning swap... do not hold this PR on it... open a follow-up issue".

Possible remediations to evaluate

Not a fixed shape -- needs design. Candidates that surfaced during #1319 discussion:

  • Resolver-time advisory log on the success path when cross_repo_misconfig_risk is non-None AND validation succeeded. Lower severity than the failure-path warning; visible under --verbose only. Trade-off: still some noise on the legitimate cross-host path, but it is gated behind explicit verbosity.
  • Lockfile annotation noting "resolved against github.com despite *.ghe.com marketplace origin" so apm audit / future drift checks can flag it post-install.
  • Marketplace publisher-side validation that rejects bare cross-repo repo fields on enterprise marketplaces and requires host qualification (corp.ghe.com/owner/repo or github.com/owner/repo). Breaks existing manifests that rely on the implicit github.com default for cross-host deps -- needs a migration window.
  • Resolved-host pinning in marketplace.json -- add a host: field at plugin entry level so the resolver does not have to infer. Schema change.

None of these are obviously correct; they trade off compatibility, verbosity, and security boundary location differently.

Related

Reproduce (without actual attack)

The success path can be locally reproduced by mocking _validate_package_exists to return True:

# tests/integration/test_ghe_marketplace_install_e2e.py
# (the legitimate cross-host case -- same code path attackers would exploit)
def test_legitimate_cross_host_validation_passes_no_hint(self, capsys):
    ...
    with patch("apm_cli.commands.install._validate_package_exists", return_value=True):
        _resolve_package_references(["shared-tool@my-marketplace"], ...)
    # No hint, install proceeds. If the validated repo were attacker-staged,
    # the operator would see no signal at any layer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority/highShips in current or next milestonestatus/needs-triageNew, awaiting maintainer review.theme/securitySecure by default. Content scanning, lockfile integrity, MCP trust boundaries.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions