disco is a CLI for scanning AWS, Azure, and GCP accounts into a local SQLite database, including the relationships between resources. After scanning, you query the DB offline: which Lambdas read a given secret, what an IAM role is attached to, which RDS clusters share a KMS key. Built for security and compliance work that needs full inventory, not the slice a console search returns.
scanwalks an AWS account, Azure subscription, or GCP org and writes every resource it finds.scanalso runs a resolve phase that connects resources with typed edges (contains,uses,attached-to,routes-to,assumes,peer,bounded-by, pluscross-account-trust/cross-sub-rbac/cross-project-iam/org-iam).resources,diff,graph,check,findings,history,coverage,summary,tag-coverage,scans,snapshot, andverifyquery the local DB without going back to the cloud.
Those services are convenient but incomplete. disco calls each cloud's per-service SDK directly, so the things unified APIs skip still show up in the graph: KMS grants, EFS mount targets, CloudFormation-managed resources, IAM Identity Center assignments, Entra ID identities, GCP VPC Service Controls perimeters, and plenty more.
A scan takes a few minutes. After that, queries run sub-second against a few-thousand-resource DB, and cloud APIs only get hit at scan time, so resources, graph, and check work the same on a plane as they do on the office network.
The queryable verbs speak JSON, JSONL, CSV, SARIF, DOT, and Mermaid. resources -o json, graph complete -o json, and check -o json produce identical bytes across runs (same SHA-256), so they're safe to commit, diff, and feed into CI. disco check --output sarif drops straight into GitHub code scanning. disco snapshot packs the DB into a single file with a manifest and inner-DB hash for handoff, and disco coverage services --check-strict flags new cloud resource types disco doesn't yet scan.
Build needs Go. No C toolchain: the SQLite driver is pure Go (modernc.org/sqlite), so CGO_ENABLED=0 is required. The Makefile sets it.
make build # → ./disco, version-stamped from `git describe`
make all # fmt + vet + test + build
make test # CGO_ENABLED=0 go test ./...
make dist # cross-compile linux/darwin/windows amd64+arm64 into dist/Plain go build works too if you skip the Makefile, but the version stamp falls back to dev (the -X cmd.Version ldflag is injected by make):
CGO_ENABLED=0 go build -o disco .disco
├── scan
│ ├── aws per-account, --regions, --profile, --skip-globals
│ ├── azure per-subscription via DefaultAzureCredential
│ └── gcp per-project fan-out across reachable projects
├── resources filter by --type, --providers, --regions, --discovered-since
│ └── show <id|name> full record for one resolved resource
├── diff <from> <to> resources added / gone stale between two scan runs
├── history <id|name> every recorded version of one resource, oldest→newest
├── graph
│ ├── blast <id> reachability from a seed
│ ├── path <A> <B> shortest path between two resources
│ └── complete full graph (every resource + edges)
├── check OPA Rego policy eval; --packs, --rules, --output sarif, --persist
├── findings query findings recorded by `check --persist`
│ ├── list persisted findings (defaults to latest run)
│ └── runs recorded check-run history
├── coverage scanner-declared types vs cloud registry; --check-strict
├── summary portfolio rollup
├── tag-coverage per-tag coverage rate
├── scans
│ └── show <id> detail for one recorded scan run
├── snapshot <out> freeze DB into single-file archive (.zip|.tar.gz|.tar.xz)
├── verify <archive> verify snapshot manifest + inner-DB SHA-256
├── config
│ └── init write a boilerplate config.yaml
└── completion shell completion script (bash|zsh|fish|powershell)
Every command takes --db / --config / -v. --db-readonly opens the DB read-only and rejects any write path (scan, snapshot to a write-locked DB, etc.).
# Scan
disco scan aws --profile myprofile --regions us-east-1,us-west-2
disco scan azure --services azure:compute,azure:network
disco scan gcp --services gcp:compute,gcp:storage
# Query
disco resources --type aws:ec2:instance --regions us-east-1,us-west-2
disco resources show i-0abc123 -o json
disco graph <resource-id> --kinds contains --depth 2 --output dot
disco coverage services --providers aws
# Policy check; --rules accepts a custom Rego directory
disco check --packs aws-waf --output sarif > findings.sarif
# Evidence archive; format follows the extension (.zip, .tar.gz, .tar.xz)
disco snapshot evidence-2026-05-06.tar.xz
disco verify evidence-2026-05-06.tar.xzAzure scans every accessible subscription, GCP fans out across accessible projects. Override via config. Resource types are lowercase cloud:service:kind: aws:ec2:instance, azure:compute:virtual-machine, gcp:compute:instance.
The CLI is shaped around a handful of recurring jobs. Each block is one tested invocation; chain with jq / awk / your own scripts as needed.
One-page count of the estate by provider, account, region, and resource type, with an as-of timestamp from the most recent scan. Output is also available as JSON or CSV for slide builders.
disco summary
disco summary -o json | jq '.by_account, .by_provider'Coverage rate per tag key, which is the precondition for chargeback and showback. Zero-coverage keys still appear so dashboards see the absent-tag signal. Tag KEYS shaped like AWS access-key IDs are flagged [suspicious:aws-access-key-id] so credential paste-into-tag mistakes don't render as legitimate scorecard rows.
disco tag-coverage owner cost-center environment --case-insensitive
disco tag-coverage --type aws:ec2:instance -o json | jq '.[] | select(.coverage<0.95)'OPA Rego evaluation against the local resource DB. SARIF 2.1.0 output feeds directly into GitHub, GitLab, or Sonar code-scanning ingest. Any reported finding gates the exit code at 1 by default, so wiring disco check into a CI step needs no extra flag. --exit-zero opts out for inventory-only runs that should publish findings without breaking the pipeline. partialFingerprints keeps repeat findings de-duped across runs. BYO Rego via --rules ./policies/; bundled aws-waf is a 5-rule sample pack.
disco check --packs aws-waf --severity high -o sarif > findings.sarif
disco check --rules ./policies --tag waf_pillar=security -o table
disco check --packs aws-waf --exit-zero -o sarif > inventory.sarif # render onlyBFS reachability from a seed. Partial-ID lookup means short IDs pasted from a ticket / CloudTrail line resolve cleanly. IAM principals receive edges rather than emit them, so graph blast auto-expands to --direction both for them.
disco graph blast 8895a0bd # short ID from a ticket
disco graph blast my-role --provider aws --type aws:iam:role --depth 4 -o dot | dot -Tpng > blast.pngEvery disco scan records a row in scans. --scan-id latest resolves to the most-recent scan that touched rows. --scan-as discovered|verified|any picks which scan-FK column the filter targets: discovered for "what's new this run", verified for "what this run re-verified."
disco scans
disco resources --scan-id latest --scan-as discovered
disco resources --discovered-since 2026-04-01 -o json | jq 'length'disco snapshot freezes the DB into a single archive (.zip, .tar.gz/.tgz, .tar.xz/.txz) with a manifest carrying the inner-DB SHA-256, generated-at timestamp, and scan IDs. --db-readonly guarantees the source DB is not mutated. --signing-payload emits the canonical manifest bytes for an external signer (openssl pkeyutl -sign, minisign, ssh-keygen -Y sign, cosign). The receiver's disco verify --signature --pubkey validates the detached ed25519 signature.
# Producer
disco --db-readonly snapshot /tmp/audit-2026-q2.tar.gz --signing-payload /tmp/audit.payload
openssl pkeyutl -sign -inkey priv.pem -rawin -in /tmp/audit.payload -out /tmp/audit.sig
# Auditor
disco verify /tmp/audit-2026-q2.tar.gz --signature /tmp/audit.sig --pubkey ed25519.pemEvery tagged release ships a Software Bill of Materials per binary, in both formats:
disco-<version>-<os>-<arch>.cdx.json (CycloneDX 1.7) and .spdx.json (SPDX 2.3), alongside
the existing .sha256 sidecar. They are generated by syft
from the binary's Go buildinfo (before compression), so they list exactly the modules linked
into that binary. Feed the CycloneDX doc to Dependency-Track / Grype, or the SPDX doc to
license/compliance tooling. Generate the same locally with make sbom (writes to dist/).
Tagged releases are gated on govulncheck:
the CI test job runs a symbol-level reachability scan against the same build config disco ships
(-tags grpcnotrace, CGO_ENABLED=0), and a reachable known vulnerability fails the job so
no draft release is cut. The vulnerability DB (vuln.go.dev) is queried
live at scan time. Run the same check locally with make vulncheck.
The scan uses -mode binary over a freshly built binary rather than source mode — source mode
builds whole-program SSA across disco's ~496-module dependency graph (three full cloud SDKs) and
needs more than 23GB of RAM. Binary mode reads the symbol table instead, keeping the same
symbol-level precision under 8GB.
coverage --check-strict exits non-zero whenever the scanner-declared type list disagrees with the live cloud-provider registry (CloudFormation ListTypes / Azure ARM Providers/List / GCP Discovery API). Pair with --resolvers --only-unannotated to surface resolvers with zero declared EdgeDecl. Those are the candidate sweep targets for closing graph gaps.
disco coverage services --check-strict --providers aws
disco coverage resolvers --only-unannotated --providers aws -o json | jq '.[].resolver'graph complete --orphans-only keeps only nodes with zero in/out edges in the returned set. That surfaces unattached EBS volumes, key-pairs no instance uses, and IAM principals with no group or policy attachments.
disco graph complete --orphans-only -o json | jq -r '.nodes[].resource | [.type, .name, .native_id] | @tsv'Config lives at $XDG_CONFIG_HOME/disco/config.yaml (Viper format). On Linux that's ~/.config/disco/config.yaml; macOS and Windows use the platform app-data dir. Any key can be overridden with a DISCO_-prefixed env var. The DB defaults to $XDG_DATA_HOME/disco/disco.db (~/.local/share/disco/disco.db on Linux); override with --db or $DISCO_DB.
cmd/<subcommand>.go → internal/providers/<aws|azure|gcp>/ → store/ → sqlite
(scanners then resolvers) (sqlx + squirrel)
Scanners register via init() and write rows into resources, one file per service. Resolvers run after, read those rows, and emit edges into relationships and hierarchy_closure. Edges pointing at unscanned targets skip silently rather than failing the scan, so a partial scan still produces a usable graph. Secrets are scrubbed at the store boundary in store/sanitize.go.
Per-subdirectory CLAUDE.md files document local conventions; CODE_STRUCTURE.md is the higher-level map.
All three clouds are covered broadly: hundreds of services scanned via each cloud's per-service SDK, spanning compute, storage, networking, identity, data, security, and governance. Approximate breadth (distinct resource types the running binary declares):
| Provider | Services | Resource types |
|---|---|---|
| AWS | ~300 | ~1,800 |
| Azure | ~150 | ~420 |
| GCP | ~40 | ~250 |
These lists move as services ship, so the README does not hand-maintain them. For the live, authoritative list from the binary you built, run:
disco coverage services --providers aws # or azure, gcp
disco coverage services --filter uncovered # what each registry exposes that disco doesn't yet scanDetecting drift between disco's scanners and the upstream cloud registries is exactly what the
coverage command exists for (see the coverage-gating use case above). FEATURES.md lists
shipped capabilities in prose.
CGO_ENABLED=0 go test ./...
CGO_ENABLED=0 go test ./internal/providers/aws/... -run TestSomething -v
go vet ./...The primary branch is main. Feature branches fork from main and merge back into it.
Tagged v* pushes trigger the GitHub Actions release workflow (.github/workflows/release.yaml), which cross-compiles and
publishes binaries for linux/darwin/windows on amd64+arm64. Builds are version-stamped
from the tag. You can always build from source with make build.
Contributions are welcome. See CONTRIBUTING.md for build, test,
and branch conventions, and CODE_OF_CONDUCT.md for community
expectations. Found a security issue? Please report it privately per
SECURITY.md rather than opening a public issue.
MIT — see LICENSE.
The vast majority of this codebase was written by Claude (Anthropic) using Claude Code, under human direction and review. Architecture decisions, scope, and final commits are mine; the line-level work — scanners, resolvers, tests, edge logic, docs — is overwhelmingly Claude-authored. Co-author trailers on commits reflect this.