SafeDep Documentation Audit
The product surface is real and broad — vet, pmg, xBom, SafeDep Cloud, gryph, and a dozen integrations — but the docs drift badly against themselves: one headline feature ships under two different CLIs (out of four binaries the docs hand you with no map), the API key has four different env-var names and an optionally-required Bearer prefix, the console lives on a different host than every example assumes, and the API reference is a five-sentence stub. These are exactly the failures that break AI coding agents, which can't apply human judgment when two pages disagree.
1. The same SQL-query feature is documented under two different CLIs (critical)
Location: /cloud/quickstart vs /reference/sql-query
Problem: The Cloud quickstart documents SQL queries as a vet subcommand: vet cloud query execute --sql, with vet cloud login. The dedicated SQL reference page documents the same feature under a standalone safedep CLI: safedep query exec --sql, safedep query schema list, safedep auth login / safedep auth status. Neither page acknowledges the other tool exists.
Consequence: A developer who follows the quickstart installs and learns vet cloud query ...; a developer who follows the reference learns safedep query .... Each will get "command not found" or "unknown subcommand" if they cross between pages. An agent has no way to know which binary to install. This is the single most confusing contradiction in the docs because it's the primary invocation of a headline feature.
The fix: Pick one CLI as canonical for SQL queries, document it consistently on both pages, and if both vet cloud query and safedep query genuinely exist, add an explicit note explaining the relationship (e.g., "safedep query is equivalent to vet cloud query") on both pages.
2. API-key Authorization header contradicts itself: Bearer prefix on one page, raw key on another (critical)
Location: /cloud/authentication vs /apps/mcp/overview
Problem: The authentication page documents the header as Authorization: Bearer sk_your_api_key — a Bearer prefix with an sk_-format key. The MCP overview page sends the key raw: Authorization: <API Key>, no Bearer prefix.
Consequence: At least one of these header forms is wrong for at least one endpoint, and the docs give no way to tell which — the two pages may describe genuinely different services (the data-plane API vs. the MCP server) that legitimately accept different header shapes, but neither page says so. An AI agent copies whichever it reads and, if it picks the wrong one, fails with an auth error it can't diagnose because both forms are asserted as correct. Auth is the first thing every integrator hits.
The fix: State the exact header format once, in the API reference, and link every integration page to it. If the data plane and the MCP endpoint genuinely accept different header shapes, say so explicitly with the reason; otherwise normalize all examples to the same form.
3. The API key has four different environment-variable / secret names across pages (critical)
Location: /cloud/authentication, /cloud/quickstart, /cloud/faq, /cloud/malware-analysis
Problem: The same credentials are named inconsistently across pages:
- Quickstart & FAQ GitHub Actions secrets:
SAFEDEP_CLOUD_API_KEY/SAFEDEP_CLOUD_TENANT_DOMAIN - Authentication page GitHub Actions example:
SAFEDEP_CLOUD_TENANT(no_DOMAIN) - Malware-analysis GitHub Actions:
SAFEDEP_CLOUD_API_KEY/SAFEDEP_CLOUD_TENANT - Authentication-page env vars & malware-analysis GitLab/Jenkins:
SAFEDEP_API_KEY/SAFEDEP_TENANT_ID
So the tenant value alone appears as SAFEDEP_CLOUD_TENANT_DOMAIN, SAFEDEP_CLOUD_TENANT, and SAFEDEP_TENANT_ID depending on the page.
Consequence: A developer copies a CI snippet, sets SAFEDEP_CLOUD_TENANT, and the tool silently reads an unset SAFEDEP_CLOUD_TENANT_DOMAIN — auth fails with no obvious cause. Mixed-page setups (auth from one page, malware scan from another) are near-guaranteed to mismatch. This is the kind of env-var drift that produces hours of debugging an "invalid API key" that's really a misnamed variable.
The fix: Publish one canonical table of accepted environment variables and CI secret names (and any aliases the tooling actually honors), and rewrite every example to use those exact names. If aliases exist for backward compatibility, document them as aliases rather than scattering them silently.
4. Advertised open-source tool "gryph" has no documentation and 404s (significant)
Location: /introduction, /llms.txt, /gryph
Problem: The introduction page lists four "Our Open Source Tools," including "gryph - Audit trail for AI coding agents." But https://docs.safedep.io/gryph returns HTTP 404 (page_not_found), and gryph appears nowhere in llms.txt — no quickstart, no concept page, no reference.
Consequence: A developer (or an agent indexing the docs via llms.txt) reads that gryph exists, goes looking, and hits a dead end. There is no way to install, configure, or even understand what gryph does beyond a six-word tagline. The site advertises a product it doesn't document — a broken promise, though not one that breaks a working integration.
The fix: Either ship a gryph documentation page and add it to llms.txt, or remove gryph from the "Our Open Source Tools" list on the intro page until docs exist. If it's pre-release, label it clearly as "coming soon" instead of presenting it as a shipping tool.
5. API reference is a five-sentence stub with no endpoints, base URL, errors, or rate limits (significant)
Location: /api-reference/introduction
Problem: The entire API reference "Welcome" page is roughly five sentences describing that SafeDep offers a gRPC API with a ConnectRPC facade and OAuth/API-key auth. There is no base URL, no endpoint list, no request/response schema, no auth header example, no error or status-code documentation, and no rate-limit numbers. The only concrete endpoint paths in the docs (/v2/insights/packages, /api/v1/projects) appear on the authentication page, not in the reference. Rate limits (1000/hr data plane, 5000/hr control plane) live only on the authentication page.
Consequence: A developer cannot integrate against the API from the API reference alone — they must reverse-engineer endpoints from scattered prose on other pages. Agents that fetch api-reference/introduction.md to discover the API get almost nothing actionable. (An openapi.json is listed in llms.txt, but the human-facing reference doesn't surface or link its contents.) This is a completeness gap rather than a contradiction — the facts exist elsewhere — but the reference is the page integrators will reach for first.
The fix: Expand the API reference into a real reference: base URLs per plane, an endpoint index (ideally generated from the OpenAPI spec already in the repo), auth header examples, the error/status-code catalog, and rate-limit numbers — so all of it lives in the reference rather than on the authentication page.
6. Four separate CLIs ship with no "which binary is which" overview (significant)
Location: /reference/sql-query, /apps/mcp/overview, /cloud/endpoint-hub/package-guard, /cloud/quickstart
Problem: Across the docs a developer is told to install and run four distinct command-line tools with no page explaining how they relate: vet (vet cloud query, vet auth configure), safedep (safedep query exec, safedep auth login), pmg (pmg cloud login, pmg cloud sync), and @safedep/cli (npx @safedep/cli setup mcp install on the MCP page). Issue 1 documents the same SQL feature under both vet and safedep; the MCP page introduces yet a third install path via @safedep/cli.
Consequence: A developer — or an agent setting up the toolchain — can't tell which binary to install for a given task, whether they overlap, or whether safedep/@safedep/cli are supersets of vet. The "which CLI do I install?" problem is broader than the SQL feature and shows up at every entry point.
The fix: Add a short "SafeDep CLIs" overview that names each binary (vet, safedep, pmg, @safedep/cli), states what each is for, how to install it, and where they overlap — then link to it from every quickstart.
7. SafeDep hostnames are scattered across pages with no map of console vs. API vs. auth (significant)
Location: /cloud/quickstart, /cloud/authentication, /cloud/endpoint-hub/package-guard
Problem: The docs surface four safedep.io subdomains without ever stating which is which. The Cloud quickstart's web onboarding says "Navigate to app.safedep.io and sign up," and the Package Guard page again surfaces app.safedep.io as the web console. But the authentication page's plane table uses api.safedep.io (data plane) and cloud.safedep.io (control plane), and auth.safedep.io appears as the auth endpoint. No page maps console (app) vs. data plane (api) vs. control plane (cloud) vs. auth (auth).
Consequence: A developer configuring an integration can't tell which host to point at for the console, the API, or token exchange, and app.safedep.io vs cloud.safedep.io look interchangeable when they aren't. This is the same class of "which value goes here?" defect as the env-var and tenant findings, and it stalls setup.
The fix: Publish a single hosts table — console, data plane, control plane, auth endpoint — with the exact subdomain for each, and reference it from the authentication and quickstart pages so no page invents its own host.
8. "See pricing" links across the site point to a pricing page that doesn't exist (significant)
Location: /introduction (and per llms.txt, /cloud/malware-analysis, /apps/mcp/overview, /xbom)
Problem: The intro page's call to action — "Start for free. Expand to SafeDep for a unified platform experience. See pricing." — references pricing, and the same "See pricing" link recurs on malware, MCP, and xBom pages. But https://docs.safedep.io/pricing returns HTTP 404, and no pricing page exists anywhere in llms.txt.
Consequence: Anyone trying to evaluate the paid tier clicks "See pricing" and lands on a 404. The conversion path the docs are explicitly steering toward is broken.
The fix: Add a pricing page (or point the link to the marketing-site pricing URL with an absolute link), and verify the link resolves from every page that uses it.
9. SQL query examples use column names that don't exist in the schema reference (significant)
Location: /cloud/quickstart vs /reference/sql-query
Problem: The quickstart's "Find Critical Vulnerabilities" example filters vulnerabilities.risk = 'CRITICAL', but the SQL reference schema uses vulnerabilities.severity_rating (and vulnerabilities.severity_rating = 'CRITICAL'). The quickstart's "Identify Policy Violations" example selects policy_violations.rule_name without joining that table. The reference states every query must filter on an indexed column, yet the quickstart's examples filter only projects.version = 'main'.
Consequence: A developer copies the quickstart query and gets a "no such column" / unindexed-filter error. The quickstart — the page meant to get someone running in 10 minutes — ships queries that violate the rules its own reference page lays out.
The fix: Correct the quickstart examples to the documented schema (severity_rating, proper joins, an indexed filter column), and add a test that runs every published SQL example against the live schema so they can't drift.
10. Non-standard PURL form pkg:/npm/... will fail to parse (significant)
Location: /cloud/malware-analysis
Problem: The malware-analysis PURL examples use pkg:/npm/... with a leading slash after pkg:. The canonical PURL spec form — used by vet/quickstart and the vet README — is pkg:npm/... with no slash (e.g., pkg:npm/[email protected]).
Consequence: A developer copy-pastes the malware-page PURL into a vet PURL scan and it fails to parse, or silently doesn't match. Because the same docs show the correct form elsewhere, the error looks inexplicable.
The fix: Fix the malware-analysis examples to canonical pkg:npm/... form and grep the docs for any other pkg:/ occurrences.
11. Scorecard CEL field is written three incompatible ways on a single page (significant)
Location: /vet/advanced/policy-as-code
Problem: Within the one policy-as-code page, the Scorecard field appears as scorecard.scores.Maintained, scorecard.Score (capitalized, no .scores.), and scorecard.scores.Security — but the page's own CEL reference at the bottom lists only scorecard.scores.*. So scorecard.Score contradicts the page's own reference.
Consequence: A developer writing a policy filter doesn't know whether to use scorecard.Score or scorecard.scores.Security. A wrong field name produces a CEL evaluation error or a policy that silently never matches — and a policy that silently doesn't fire is a security gap, not just a typo.
The fix: Normalize every Scorecard reference on the page to the form in the CEL reference (scorecard.scores.*), and add the full list of valid scores keys so authors aren't guessing.
12. vet-action inputs differ across pages with no single input reference (significant)
Location: /vet/advanced/policy-as-code, /vet/quickstart, /faq, github.com/safedep/vet
Problem: The GitHub Action's inputs are documented differently on every page: policy-as-code uses policy: + fail-on-violation:; the quickstart uses cloud: / cloud-key: / cloud-tenant:; the FAQ uses exception-file: / fail-on-violation:. The policy file path also conflicts — README uses policy: ".github/vet/policy.yml" while the policy-as-code page uses policy: '.github/vet-policy.yml'. There is no single vet-action input reference.
Consequence: A developer assembling a workflow has to stitch inputs from four pages and can't tell which inputs coexist or what the defaults are. The two different policy paths mean a copied workflow points at a file that doesn't exist in their repo.
The fix: Publish one vet-action reference listing every input, its default, and whether it's required, and standardize the example policy path across README and docs.
13. PMG changelog is many releases stale relative to versions shown in examples (significant)
Location: /pmg/updates vs /cloud/endpoint-hub/package-guard
Problem: The PMG updates page is the only changelog in the docs, and its latest entry is v0.3.2 (11 Jan 2026). But the Package Guard page's example output shows PMG version: v0.9.0. The changelog also contradicts the quickstart on package-manager support: the updates page's "Supported Package Managers For Proxy" table lists pip / uv / poetry as 🕒 Planned, while the quickstart (per its scraped summary) lists them as Active for normal use.
Consequence: A developer reading the changelog believes the current version is v0.3.2 and that pip/uv/poetry support is unreleased — directly contradicted by example output showing v0.9.0. They can't tell what's actually shipped, which undermines trust in every version-gated instruction (e.g., "requires vet 1.9.7+").
The fix: Bring the changelog current through the shipped version, and clarify the proxy-mode vs normal-mode support distinction so the two package-manager tables don't read as a direct contradiction.
14. "tenant ID" vs "tenant domain" is an unresolved terminology split (significant)
Location: /cloud/endpoint-hub/package-guard vs /cloud/authentication and others
Problem: The Package Guard page prompts for both a "tenant ID" and a "tenant domain" as two separate values, and lists "Have a SafeDep Cloud tenant ID and tenant domain" as prerequisites. Every other page treats SAFEDEP_TENANT_ID as simply the tenant domain (the authentication page even sets SAFEDEP_TENANT_ID to a domain value).
Consequence: A developer can't tell whether tenant ID and tenant domain are the same thing or two distinct identifiers. If they're distinct, the other pages are wrong to conflate them; if they're the same, Package Guard is asking for one value twice. Either way, setup stalls on "what do I put here?"
The fix: Define "tenant ID" and "tenant domain" once, state whether they're the same value, and make every prompt and env var consistent with that definition.
15. GitHub App "grade.lockfile" typo, empty sections, and divergent lockfile list (minor)
Location: /apps/github/overview
Problem: The supported-lockfiles list contains grade.lockfile under Maven (Java) — a typo for gradle.lockfile. The page also has empty sections ("creates a detailed list with analysis on:" followed by nothing). Separately, the GitHub App's PyPI list diverges from the vet FAQ's: the GitHub App lists uv.lock (which the FAQ omits) but omits pyproject.toml (which the FAQ lists). Both pages do list Pipfile.lock, so that file is not a divergence.
Consequence: A developer searching the page for gradle.lockfile won't find it; one who trusts the typo may wonder if a differently-named file is required. The empty sections leave readers unsure what the PR report actually contains, and the FAQ/GitHub-App uv.lock/pyproject.toml mismatch makes it unclear which Python lockfiles are truly supported where.
The fix: Fix grade.lockfile → gradle.lockfile, fill in the empty "analysis on:" section, and reconcile the GitHub App and vet FAQ Python support lists (or note explicitly that the GitHub App supports a different subset than the vet CLI).
16. README internal contradiction: global vs non-global npm install (minor)
Location: github.com/safedep/vet
Problem: The README's Quick Start installs npm install -g @safedep/vet (global), but its Installation section installs npm install @safedep/vet (non-global). A non-global install does not put vet on PATH, so subsequent vet ... commands fail. The README also links "GitHub Integration" to the bare docs root and references repo-relative ./docs/*.md paths that don't exist on the docs site.
Consequence: A developer following the Installation section's non-global command then runs vet version and gets "command not found," with no hint that the missing -g is the cause. The dead ./docs/*.md links send readers nowhere.
The fix: Use npm install -g @safedep/vet consistently (or document the npx/local-bin path), and convert repo-relative doc links to absolute docs.safedep.io URLs.
17. Config-directory location is inconsistent across tools (minor)
Location: /cloud/faq, /cloud/endpoint-hub/package-guard, /pmg/quickstart, /pmg/updates
Problem: The config location differs by page: the Cloud FAQ uses ~/.config/vet/; Package Guard uses ~/.config/safedep/pmg; the PMG quickstart points at ~/.pmg.rc; the PMG updates page says trusted packages live in config.yml. There's no single statement of where each tool's config lives.
Consequence: A developer editing config to add a trusted package or fix auth can't tell which directory the tool actually reads, and may edit a file the tool ignores.
The fix: Document each tool's config path explicitly (vet vs pmg vs safedep CLI), and if ~/.pmg.rc and ~/.config/safedep/pmg/config.yml are both honored, say which takes precedence.
18. A legacy API entry-point path /api/introduction 404s (minor)
Location: /api/introduction
Problem: The path https://docs.safedep.io/api/introduction returns HTTP 404. The live API page is at /api-reference/introduction, and llms.txt lists only the api-reference path — so the shorter /api/introduction is a plausible-but-dead URL.
Consequence: Anyone who guesses or arrives at the shorter /api/introduction path (a natural guess for an API entry point) lands on a 404 instead of the API reference.
The fix: Add a redirect from /api/introduction to /api-reference/introduction so the obvious-guess path resolves.
19. Privacy claim sits uneasily next to documented static code analysis (minor)
Location: /faq
Problem: The vet FAQ states "No source code is ever analyzed or transmitted," yet vet and xBom are documented elsewhere as performing static code analysis and "dependency usage identification" — features that, by name, analyze source.
Consequence: A security-conscious developer reading the privacy claim and then the static-analysis features can't reconcile them, which undermines confidence in the privacy posture exactly where it matters most.
The fix: Clarify the scope precisely — e.g., distinguish analyzing dependency source from transmitting the user's source — so the privacy claim and the static-analysis features don't appear to contradict.
What they do well
- An
llms.txtexists and is a real index — every documented page is enumerated with descriptions, which is more agent-friendly than most docs sites, and anopenapi.jsonis published alongside it. - CEL/policy guidance has genuinely useful specifics — e.g., "use
size()notlength()" — even if it's buried in FAQ prose rather than the policy reference. - The data-plane vs control-plane auth model is clearly articulated on the API reference (API keys for data plane, OAuth for control plane), giving integrators a correct mental model even where the header details drift.
Top 3 recommendations
- Resolve the credential and CLI contradictions first — one canonical Authorization header, one set of env-var/secret names, and one CLI for the SQL feature (issues 1–3). These silently break agents and CI and are the highest-leverage fixes.
- Publish the missing maps — a "which CLI is which" overview (issue 6) and a single hosts table for console/data/control/auth subdomains (issue 7), so developers and agents stop guessing which binary and which domain a task needs.
- Turn the API reference into a real reference and fix the broken promises — endpoints (from the existing OpenAPI spec), base URLs, error codes, and rate limits in one place (issue 5); and ship or de-list gryph and add the pricing page the CTAs point to (issues 4, 8).