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

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions