safedep query command runs SQL-like queries against SafeDep Cloud’s analytics surface:
the packages, projects, endpoints, events, and security findings collected across
your tenant, enriched with global threat intelligence (vulnerabilities, EPSS,
CISA KEV, OpenSSF Scorecard, and more).
Prerequisites
Install the CLI and sign in to SafeDep Cloud:Your first query
Start by seeing what you can query. List the tables, then inspect one to view its columns, capability flags, and join edges:WHERE, sort
with ORDER BY:
- Write every column as
table.columnusing the real table name. No table aliases, no bare column names. Result aliases viaASwork (COUNT(...) AS n, thenORDER BY n). - Every query must filter on an indexed column or a bounded time range. Here
projects.origin_sourceis indexed, so the query is accepted. This rule keeps queries cheap; the schema marks which columns are indexed.
safedep query exec reads the statement from --sql, --sql-file, or stdin:
How the data is organized
Tables come in two kinds:- Tenant tables hold your data:
projects,project_versions,boms,packages,endpoints, thecomponent_*finding tables, and the event tables (inventory_events,package_guard_events). - Join-only enrichment tables hold global reference data shared across all
tenants:
vulnerabilities,epss,kev,licenses,malware_analysis,open_source_packages,open_source_projects,scorecards,scorecard_checks,terraform_providers.
What SQL we support
| Feature | Notes |
|---|---|
SELECT | Columns must be qualified table.column. |
WHERE | =, <, <=, >, >=, AND, OR. Enums filtered by name. |
LIKE | Prefix only: 'name%'. Leading wildcards ('%name') are rejected. |
JOIN | Needs an ON clause, but its contents are ignored: the catalog applies a predefined join. Only join tables connected by an edge. |
GROUP BY | Only columns flagged groupable. |
HAVING | Filter on aggregates after grouping. |
ORDER BY | Including by an aggregate alias. |
| Aggregates | count, sum, avg, min, max. |
WITH), UNION and set operations, window
functions, casts, arbitrary functions, and multiple statements.
Discovering the schema
You do not need to memorize tables or columns. The schema is self-describing, and this is the entry point for both humans and AI agents.| Flag | Meaning |
|---|---|
selectable | May appear in SELECT. |
filterable | May appear in WHERE. |
groupable | May appear in GROUP BY. |
aggregatable | May be wrapped in sum/avg/min/max. |
indexed | Satisfies the “must filter on an indexed column” rule. |
schema get also prints the join edges (which tables connect, and their
cardinality) and the usage rules with example queries, so one call gives you
everything needed to write a valid query.
Letting an AI agent write the queries
Point your AI coding agent at thesafedep CLI. The schema is self-describing
and server errors come back verbatim, so the agent can discover tables, write a
query, and self-correct on its own.
Prompt:
Examples
Severity breakdown for one ecosystem
Severity breakdown for one ecosystem
Anchor on the indexed
ecosystem column (an OR across all severities would not
satisfy the index rule):Top remediation targets by critical CVE count
Top remediation targets by critical CVE count
Enrich critical CVEs with EPSS exploit prediction
Enrich critical CVEs with EPSS exploit prediction
EPSS is join-only enrichment, so anchor in the tenant
packages table and join
out. Sorting by EPSS surfaces the CVEs most likely to be exploited:Cross-reference inventory against CISA KEV
Cross-reference inventory against CISA KEV
kev is the CISA Known Exploited Vulnerabilities catalog. Use a bounded range on
the indexed kev.date_added to anchor the query:Malware findings across your inventory
Malware findings across your inventory
is_malware is not indexed, so anchor on the indexed detected_at timestamp:List declared licenses for your packages
List declared licenses for your packages
Walk from packages through
component_licenses to the global licenses catalog
for SPDX metadata. Anchor on the indexed packages.ecosystem:Your endpoint fleet, by most recent sync
Your endpoint fleet, by most recent sync
endpoints are the machines and CI runners reporting to your tenant. The
timestamp columns are indexed, so a bounded range anchors the query:endpoint_type and trust_level enums are not groupable, so use WHERE
filters and listings rather than GROUP BY for endpoints. Filter them by name
(endpoint_type = 'CI_RUNNER').Which endpoints have blocked any package
Which endpoints have blocked any package
For “which endpoints…” questions you want a de-duplicated list, not one row per
event.
endpoints.identifier is not groupable, so use SELECT DISTINCT:What each endpoint blocked
What each endpoint blocked
Join Package Guard events to the endpoint that produced them, newest first, for a
full audit trail:The
package_ecosystem enum renders as its stored number in table output
(2 = ECOSYSTEM_NPM, 3 = ECOSYSTEM_PYPI); use -o json for the enum name.Shadow AI inventory: MCP servers observed across endpoints
Shadow AI inventory: MCP servers observed across endpoints
inventory_events captures items discovered by vet on each endpoint. Filter on
the indexed item_kind to find AI-related items:Output modes
Select with-o / --output. When omitted, the CLI auto-detects the format: a
rendered table for an interactive terminal, plain text when piped.
| Mode | Use it for |
|---|---|
table | Human reading. Adds a footer: <rows> | ~<cost> cost | <ms>ms, plus a next page hint when more rows exist. |
plain | Pipelines. Tab-separated header and rows, no footer. |
json | Scripts and agents. Typed columns, planner stats, and next_page_token. |
Pagination
Pagination is caller-driven; the CLI does not auto-iterate. Fetch the first page, then re-run the same query with the returned token:Troubleshooting
| Error | Cause | Fix |
|---|---|---|
queries must reference at least one tenant table | Query starts from a join-only enrichment table. | Anchor FROM on a tenant table (for example packages) and join out. |
query must filter on an indexed column or a bounded time range | No effective indexed filter, or an OR across all enum values. | Add an equality filter on an indexed column, or a bounded timestamp range. |
column "X" is not groupable | GROUP BY on a non-groupable column. | Group only by groupable columns; use SELECT DISTINCT for “which” questions. |
column "X" is not aggregatable | sum/avg/min/max on a column without the flag. | Use count(column), or aggregate only aggregatable columns. |
LIKE pattern rejected | Leading wildcard ('%name'). | Use a prefix pattern ('name%'). |
| Duplicate rows | Join fan-out from many_to_one edges. | Use COUNT(DISTINCT ...) or de-duplicate client-side. |
| Unknown table or column | Name not in the schema. | Re-run safedep query schema get and use exact names. |
SafeDep Cloud Overview
What SafeDep Cloud collects to build the data you query.
Authentication
Sign in with
safedep auth login before running queries.vet Filtering
Filter findings locally with vet’s CEL query language.
API & Automation
Programmatic access to SafeDep’s APIs.

