Skip to content

Packages search

If you’re a security lead investigating a BLOCKED install, or a dev trying to figure out why npm install some-package 403’d against the proxy, this is the page you open. Navigate to Orgs → your org → Packages (URL: /admin/ui/orgs/<slug>/packages). You’ll see a breadcrumb (Orgs / <slug> / Packages), a heading Packages, and a coached empty state pointing at three example queries: lodash, react@18.2.0, pypi:requests. The search box is autofocused — start typing and the next action is a working search.

One field, labelled query, placeholder lodash or @scope/pkg@1.0.0. Submit posts a GET to the same URL with ?q=…, so every search is a shareable link. The parser accepts three shapes:

  • name — plain package name. Returns the full summary (allowlist / blocklist / assessed versions / recent audit) but no live-verdict tile.
  • name@version — name plus a pinned version. Same summary, plus the Live verdict tile (see below).
  • <eco>:name[@version] — explicit ecosystem prefix. Today’s prefixes are npm:, pypi:, cargo:, nuget:, maven:, go:. Without a prefix the query defaults to npm:. Scoped npm names like @scope/pkg@1.0.0 work because the parser splits on the last @.

If the parser rejects your input the page re-renders with the box still focused and a red class="error" line above the empty state. The exact strings:

  • Enter a package name to search.
  • Could not look up `<query>` — try `name` or `name@version`.
  • Unknown ecosystem `<eco>:` in `<query>` at column <n>. Known prefixes: <known>.
  • `<eco>:` prefix in `<query>` has no package name — try `<eco>:lodash` or `<eco>:lodash@4.17.20`.

When the query parses but matches no records (a typo in the package name, or a package nobody on this org has ever installed), you’ll get a one-line empty state — No records for <name> in this org. — instead of an error. That’s a successful lookup against zero data, not a parse failure.

When the org has at least one team configured, a multi-select labelled Team sits beside the query box. Selected teams narrow the Recent audit fragment only — allowlist, blocklist, and assessed-versions stay org-wide because they’re per-package facts, not per-actor. The (no team) sentinel matches rows whose actor identity doesn’t bridge to any team (most often: API keys that aren’t pinned to a team yet). Selections round-trip through the URL as repeated team_id= params, so a filtered view is shareable.

If the org has no teams yet the dropdown doesn’t render at all.

Renders only on name@version queries. Shows one line:

The proxy would currently resolve <name>@<version> for org <slug> as SAFE / BLOCKED / QUARANTINED.

…followed by a muted footnote naming the call (Computed via resolveVerdicts against the org's policies and the current blocklist / cache / package_versions state.). This is the same code path the proxy runs at install-time, against this org’s policy stack, right now. It’s the closest thing the dashboard has to a dry-run: if it says BLOCKED here, the next install attempt will 403.

For why it’s BLOCKED, scroll to the panels below.

Eyebrow ALLOWLIST, heading Allowlist matches. Columns: package, version range, reason. Each row is a hit against this org’s allowlist for the searched package name. An empty allowlist panel doesn’t render — absence means no override.

If a package is allowlisted you should generally expect the Live verdict above to read SAFE, because the policy layer short-circuits on allowlist hit. The exception: a narrower version range than the one you queried — the allowlist row will still display, but a version outside the range won’t pick it up.

Eyebrow BLOCKLIST, heading Blocklist matches (n). Columns: scope, source, package, version range, severity, summary.

  • scope is org (a hit from this org’s own blocklist) or global (a hit from the Upwarden feed — OSV / GHSA, or the operator-managed pool).
  • source names the producing system (osv, ghsa, manual:<actor>, etc.).
  • severity is the advisory severity when one exists (LOW / MEDIUM / HIGH / CRITICAL); blank for manual blocks without a severity.

These rows are the reason a package shows up as BLOCKED. If you don’t see anything here but the live verdict still reads BLOCKED, the cause is one of cooldown, the size-jump / fingerprint pre-scan layer, or scanner verdict — keep reading.

Eyebrow ASSESSED VERSIONS, heading Assessed versions (n). The denormalised view of every version this org has ever resolved a verdict for, plus harvested manifest metadata. Columns: ecosystem, version, verdict, published, author, deps, source.

The verdict column is where the page gets dense. Each row’s verdict pill (SAFE, BLOCKED, QUARANTINED) can carry up to two chips beside it:

  • Fingerprint chip (#566) — matches <tool> <version-range>. Renders when the version trips a registered build-tool fingerprint (esbuild, swc, sass-embedded, …). The tooltip carries the remediation text — paste it into a PR comment. Use this chip to recognise “the block is really about the upstream tool, not the transitive package”.
  • Size-anomaly chip (#673) — size anomaly <reason> <observed-size>. Renders when the pre-scan stamped a sizeAnomalyReason on the version. Today’s reasons:
    • size_jump_5x_median — the tarball is more than 5× the median of the three most recent prior versions. Historically a strong supply-chain compromise tell (event-stream@3.3.6, ua-parser-js@0.7.29).
    • tarball_too_large — the tarball hit the 200 MiB cap; observed size may be absent because we never finished measuring.
    • The chip’s title carries the raw reason token so a SIEM pivot or table filter can use it without re-deriving from the pill label.

source names the verdict producer: proxy:<ecosystem>:tarball / :manifest, osv-block, ghsa-block, cooldown, policy-allowlist, policy-denylist, fingerprint, size-jump, scanner. The same set of values shows up in the audit log’s verdictSource column.

Eyebrow AUDIT TRAIL, heading Recent audit (n). Up to 25 of the most recent audit rows for this package in this org (narrowed by the team filter if set). Columns: when, decision, version, reason. The reason cell shows the decisionReason envelope text followed by the verdictSource in muted parens.

Use this panel to answer “did anyone actually try to install this in the last day?” before chasing a verdict that might not be biting anyone yet. If the audit fragment is empty but the live verdict is BLOCKED, the block is theoretical — no real install has hit it. If the fragment is full of BLOCKED rows, you have an active developer-pain problem and should sort out a policy override or version pin.

For deeper queries — longer windows, cross-package, by actor — drop into the JSON admin API; see Querying the audit log.

The dashboard route hits GET /api/v1/admin/orgs/:slug/packages?q=…&team_id=…. The endpoint is gated on the policy:r capability, so a key without it gets 403’d at the API even if the operator can browse to the page. If the JSON endpoint 400s on a query the local parser accepted, the page falls back to Could not look up `<query>` — try `name` or `name@version`.

  • Verdicts & the scanner — what SAFE / BLOCKED / QUARANTINED actually mean on the wire.
  • Silent rollback — why a ^1.x install can still succeed even when a specific version is BLOCKED on this page.
  • Querying the audit log — JSON-API patterns when 25 rows on this page aren’t enough.
  • Policy overrides — how to allowlist a specific version when this page says BLOCKED but your team has accepted the risk.