Security
Built for production. Designed to stay in your lane.
Dino runs on your machine. Your API schema, scan results, and secrets never leave the directory you run it in. Every claim on this page cites a file path in the Dino source so you can verify it yourself.
Execution model
Dino is a CLI you run on your own machine or inside your own CI. There is no Dino-hosted scanner, no cloud service that sees your traffic, and no upload step.
When you run dino scan, Dino makes HTTP requests only to the API endpoint you configured in .dino.yml (your own API under test). It does not contact any Dino-controlled servers during a scan.
Scan reports and historical snapshots are written to local directories: .dino/reports and .dino/snapshots, inside your project, per packages/core/src/config/defaults.ts:33-34. Nothing is sent anywhere.
Analytics events (command invoked / completed / failed) are wired to a console adapter in the shipped CLI, per packages/cli/src/shared/base-command.ts:240,255. They are logged to your terminal for local debugging and are not transmitted anywhere. A PostHog adapter exists in the @dino/analytics package but the CLI does not wire it up.
SSRF protection
Before Dino makes any request on your behalf, the target URL goes through a static check against private, reserved, loopback, and cloud-metadata ranges. Implementation at packages/core/src/tenant/tenant-loader.ts:162-196.
Blocked IPv4 ranges include: 0.0.0.0/8, 127.0.0.0/8 (loopback), 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (RFC 1918), 169.254.0.0/16 (link-local including the AWS metadata endpoint), 100.64.0.0/10 (CGNAT), and the IETF-reserved test and benchmark ranges (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 198.18.0.0/15, 240.0.0.0/4). Full list at packages/core/src/tenant/tenant-loader.ts:83-99.
Cloud-metadata hostnames are blocked by name, not just by IP: metadata.google.internal and the IPv6 AWS metadata host fd00:ec2::254. See packages/core/src/tenant/tenant-loader.ts:58-62.
Dino also performs runtime DNS resolution to prevent DNS rebinding SSRF bypasses: the actual resolved IP is checked against the same blocklist at request time, not just the hostname at config load time. See packages/core/src/tenant/tenant-loader.ts:219-229.
Only http:// and https:// protocols are allowed. Anything else ( file://, gopher://, data://, etc.) is rejected at config load time.
Secret handling
Credentials are never stored in .dino.yml. The config schema only accepts a credentialRef string: a reference to an environment variable name that Dino reads at runtime. The raw secret value never touches disk in the Dino config. See packages/core/src/tenant/tenant-loader.ts:38.
Dino's AI reasoning module (used for optional explanation of findings) runs every prompt through a sanitizer that strips API keys, JWTs, Bearer tokens, and URL credentials before any third-party model call. Implementation at packages/reasoning/src/sanitize.ts:98 and used in the reasoning engine at packages/reasoning/src/engine.ts:10,124 and prompt helpers at packages/reasoning/src/prompts/_shared.ts:45.
Error messages written to scan reports and analytics events also run through a sanitizer that redacts secret-like tokens, per packages/core/src/utils/error-sanitizer.ts. That means that even if your API returns a stack trace with an embedded token, Dino redacts it before it lands in .dino/reports.
What Dino does NOT do
The fastest way to trust any layer in your stack is to know what it refuses to do. Every bullet below is a negative claim: you can verify it by grepping the Dino source and NOT finding the code that would make it false.
- Dino does not send your API schema, scan results, or any request or response data to any Dino-controlled server. No telemetry endpoints, no upload URLs, no cloud push. The CLI uses a console analytics adapter only.
- Dino does not modify your API. Every scan is read-only at the protocol level:
dino scanissues GET, HEAD, and introspection queries. It does not perform writes, deletions, or destructive operations against your running service. - Dino does not modify your codebase. It reads your API's schema and writes reports to
.dino/reportsand snapshots to.dino/snapshotsin your project directory. It does not write to your source files, open pull requests, or commit to git. - Dino does not execute code on your behalf beyond the CLI commands you run. It is a CLI. There is no background agent, no daemon, no auto-runner.
- Dino does not retain scan results outside your project. There is no server-side history. If you delete
.dino/from your project, the only copy of your scan history is gone. - Dino does not share data between tenants. Each tenant's config, credentials, and history are scoped to that tenant's ID.
- Dino does not auto-update. The npm package ships a specific version, and
@dino-hq/cli@x.y.zis exactly what runs on your machine until you update it. - Dino does not phone home for license checks, usage metering, or feature gating. There is no account system in the CLI today.
Responsible disclosure
Found a security issue? Email hello@usedino.dev. We aim to acknowledge reports within 48 hours. Please do not disclose publicly until we have had a chance to investigate and patch.
We do not operate a paid bounty program for security reports today. We will credit reporters in the fix commit and the public changelog at usedino.dev/changelog when the fix ships.