-
Notifications
You must be signed in to change notification settings - Fork 0
Document the platform claim trust model across GitHub, npm, and PyPI #111
Description
Context
We have three platform claim flows with different verification mechanisms. The security properties are equivalent at the system level (namespace claims are always independently verified), but the platform claims themselves have different strength levels. This should be documented so users and security reviewers understand the trust model.
Trust model
Platform claim strength (identity linking)
| Platform | Verification method | What it proves | Weakness |
|---|---|---|---|
| GitHub | OAuth device flow + Gist proof | You approved in your browser AND published to your account | Attacker needs your browser session |
| npm | Token verification via /-/whoami |
You possess a valid npm token for this account | Attacker with stolen npm token can claim (being migrated to signed-claim model per #107) |
| PyPI | Self-reported username + DID-signed claim | You possess the device signing key | Attacker with device key can self-report any username |
Namespace claim strength (package ownership)
| Platform | Verification method | What it proves | Can stolen credential bypass? |
|---|---|---|---|
| GitHub (cargo) | crates.io owners API | GitHub username is listed as a crate owner | No — public API can't be faked |
| npm | npm registry maintainers list | Username appears in package maintainers | No — public API can't be faked |
| PyPI | PyPI JSON API (pypi.org/pypi/{pkg}/json) |
Username appears as Owner/Maintainer in roles, or GitHub owner matches | No — public API can't be faked |
System-level security (platform claim + namespace claim combined)
| Attack scenario | GitHub | npm (after #107) | PyPI |
|---|---|---|---|
| Attacker steals CI token (e.g., LiteLLM attack) | Can't claim — needs OAuth browser approval | Can't claim — needs device signing key | Can't claim — needs device signing key |
| Attacker steals device signing key | Can fake platform claim, but OAuth blocks them at claim time | Can fake platform claim + self-report any username, but namespace check blocks them | Can fake platform claim + self-report any username, but namespace check blocks them |
| Attacker steals device key + claims namespace | Blocked by OAuth (can't complete platform claim) | Blocked by public API maintainer check | Blocked by public API maintainer check |
| Attacker compromises the ecosystem registry itself (e.g., adds themselves as maintainer) | Namespace claim succeeds — but this is a different attack class (registry compromise, not credential theft) | Same | Same |
Key insight
The platform claim is the weaker link. PyPI's platform claim is the weakest (self-reported username), and GitHub's is the strongest (OAuth + published proof). But the platform claim alone grants nothing — it only enables namespace claims, which are always independently verified against public APIs.
The security property that matters: a stolen CI token cannot produce a valid namespace claim. This is true for all three platforms because:
- The platform claim requires a device signing key (stored in macOS Keychain / Linux Secret Service, not in CI)
- The namespace claim checks a public API that the attacker can't manipulate
Why PyPI doesn't use token verification
PyPI's /danger-api/echo endpoint (the equivalent of npm's /-/whoami) is an unstable v0 API that returns 403 for standard API tokens in production. Rather than depend on an unreliable endpoint, we use the signed-claim model where the real verification happens at namespace claim time via the public PyPI JSON API.
This is actually more secure than token verification because a stolen PyPI token (the LiteLLM attack vector) cannot be used to claim a platform identity. The device signing key is required, and it lives in the platform keychain — not in CI secrets.
Where to document this
-
docs/security/trust-model.mdin the auths repo — full writeup with the tables above -
CLAUDE.md— brief summary of the trust model for AI assistants working on the codebase -
PlatformContextdocstring inauths-core/src/ports/namespace.rs— already partially documented, needs the trust model distinction - Hub "How it works" page — user-facing explanation of why auths protects against supply-chain attacks
- CLI
--helptext forauths id claim pypi— note that ownership is verified at namespace claim time
References
- LiteLLM supply-chain attack (March 24, 2026) — attacker stole PyPI credentials from compromised Trivy GitHub Action
- Switch npm platform claims from token verification to signed-claim model (matching PyPI) #107 — migrate npm claims from token verification to signed-claim model
- PyPI warehouse echo endpoint source: https://github.com/pypi/warehouse/blob/main/warehouse/api/echo.py