-
Notifications
You must be signed in to change notification settings - Fork 0
Online Resolution
--online is the only networked part of postmortem. For each unique
dependency it:
- asks the ecosystem's registry for the source repository
(npm's
repository, PyPI'sproject_urls, crates.io'srepository, …), - resolves it to a
host/owner/repoand pulls reputation stats from that host (stars, created-at, last activity, archived, primary language), - scores it against risk thresholds and surfaces the suspicious ones.
See Ecosystems & Hosts for the registry/host matrix.
Everything is cached under ~/.postmortem/cache/ - see Cache.
| Signal | Tier | Points |
|---|---|---|
typosquat of <pkg> |
High (red) | 45 |
starjacking (<repo> doesn't own it) (npm)
|
High | 45 |
install-script-added (npm)
|
High | 40 |
recently-created (Nd ago) |
High | 40 |
provenance-removed (npm, crates.io)
|
High | 30 |
low-stars (N★) |
High | 30 |
archived |
Medium (amber) | 30 |
new-publisher (npm, crates.io)
|
Medium | 25 |
dangling-repo (<repo> not found) (GitHub)
|
Medium | 25 |
stale (Nd idle) |
Medium | 20 |
dormant-release (Nd gap) (npm, crates.io, PyPI)
|
Medium | 20 |
newborn-package (Nd old) (npm, crates.io, PyPI)
|
Medium | 20 |
fresh-release (Nh old) (npm, crates.io, PyPI)
|
Low | 15 |
no-repository / resolve-failed / stats-*
|
Info (unchecked) | 0 |
-
Reputation signals come from the source repo's stats vs. your thresholds (
min_stars,recent_days,stale_days). -
Identity signals:
typosquat(see Typosquatting — it covers six ecosystems, offline) andstarjacking(the linked repo doesn't declare the package — its stars are borrowed). -
Provenance signals compare the installed version against the one published before it, in whichever document its registry publishes a release history in — npm's packument, crates.io's crate record, PyPI's project JSON:
install-script-added,dormant-release,new-publisher,newborn-package(first-ever release <30d),fresh-release(this version <48h — the release-age cooldown), andprovenance-removed(a version that dropped the OIDC/Trusted-Publishing attestation an earlier one had — the axios pattern;dist.attestationson npm,trustpub_dataon crates.io).See provenance coverage for which registry answers which question.
-
dangling-repo— a declared GitHub repo that 404s (deleted/renamed), so the handle is re-registerable (repojacking exposure). -
no-repositorymeans "we couldn't find a source repo to assess" - an absence of information, so it's counted as unchecked, not suspicious.
No registry answers every question, and one that cannot be asked is never reported as a clean answer — an unanswerable signal is simply absent:
| Signal | npm | crates.io | PyPI |
|---|---|---|---|
install-script-added |
yes | — | — |
dormant-release |
yes | yes | yes |
new-publisher |
yes | yes | — |
provenance-removed |
yes | yes | — |
fresh-release / newborn-package
|
yes | yes | yes |
maintainer set (--human) |
yes | — | yes |
RubyGems, Packagist and deps.dev (Java, Go) publish a package's current state rather than its history, so none of these apply there. The OS package managers have no registry of this kind at all.
The gaps are registry limits, not pending work:
-
PyPI records no per-release uploader, so
new-publisherhas nothing to compare. Its PEP 740 attestations exist but need one/integrity/{project}/{version}/{file}/provenancerequest per file, rather than riding along in a document already fetched. -
crates.io does not say whether a crate has a
build.rs, and its owner list is a separate/ownerscall.
| Ecosystem | Requests for the history |
|---|---|
| crates.io | none — the crate record fetched for the repo and license already carries every version. |
| PyPI | one. The version-pinned document postmortem fetches (a license is per-version) has no releases map; the name-only document does. |
| npm | one packument, shared with timeline. |
Every node shows a (risk:dep) pair, each 0-100:
-
risk- the package's own risk, summed from its signal points (capped). -
dep- its dependency-subtree risk: distinct flagged deps weighted by severity. Platform/scope splits of the same module (e.g.@napi-rs/nice-*) don't inflate it.
Coloring: a package flagged on its own is red/amber; a clean package that drags
in a rotten tree (high dep) is painted blue. The closing gochi's recap
aggregates the whole forest into overall risk N · dep M plus a headcount of
high-risk / suspicious / unchecked packages.
By default the node shows the repo's primary language (free - GitHub returns
it in the repo object): express@4.18.2 ★66000 (0:0) (JavaScript).
--languages fetches the full breakdown (one extra, cached, /languages
call per repo - paid once per repo, ever):
ripgrep@14.1.0 ★66000 (0:0) (Rust:95.0|Python:2.4|Shell:1.9|Other:0.7)
-
(Lang)- primary only (GitHub, free). -
(L1:%|L2:%|Other:%)- full breakdown (--languages). -
(?)- a repo resolved but the host reported no language (e.g. GitLab/Codeberg without--languages).
Without a token, GitHub's anonymous API is 60 requests/hour - the tightest budget, so postmortem fans out gently (2 workers) and wide (8) with a token. GitLab and Codeberg resolve anonymously for public repos. See Configuration.
Commands
- scan
- tree
- audit
- fix
- licenses
- why
- diff
- sbom
- system
- scripts
- hook
- watch
- timeline
- ghost
- hunt
- allowlist
- cache
Targets
Ecosystems (overview)
System managers
Windows (overview)
- WinGet
- MSIX / AppX
- Chocolatey
- Scoop
- Add/Remove Programs
- Auto-start (ASEP)
- Scheduled tasks
- Services & drivers
- Jobs & file-based
- Privilege & trust posture
- Network posture
Concepts