The Privacy Boundary: Code Never Leaves the Machine
Gadriel Code is bound to one non-negotiable principle:
All scanning, graph construction, and secret detection runs locally. The only outbound calls are explicitly enumerated, configurable, and minimal. Hard requirement — non-negotiable.
This is what distinguishes Gadriel from every SaaS code-scanning service. Snyk, Semgrep Cloud, GitHub Advanced Security, and SonarCloud all upload code (or build artifacts, or graph data) to a vendor-controlled service — they have to, because their analysis runs there. Gadriel's analysis runs on your machine, so it does not.
Related: AI-coding integration · Tiered analysis model
1. What must never leave the machine
Without explicit, per-command, user opt-in, the CLI must not send any of:
- Source code in any form — raw, tokenized, AST, or embedding
- File paths or filenames from the scanned project
- Commit messages, branch names, or git history content
- Finding bodies, finding code-context fields, or finding metadata
- Pillar scores, overall scores, SBOM contents
- Dependency lockfile contents
- Container image layer contents
- Configuration file contents (
.env,*.tf,*.yaml, …) - The
.security/audit-log.jsonlrecording AI-coding-agent round-trips
Anything not in the closed list below is forbidden by default.
2. The closed list — the only permitted outbound calls
Every outbound call the CLI makes falls into exactly one of these categories.
Every endpoint is configurable via .gadriel.toml / ~/.gadriel/config.toml
and an environment-variable override.
| # | Purpose | Default endpoint | What gets sent | What never gets sent |
|---|---|---|---|---|
| 1 | Portal auth & licensing (mandatory at startup) | app.gadriel.ai | Credentials at login; just the refresh token on refresh. JWT cached at ~/.gadriel/auth/token.jwt (mode 0600). | No code. No findings. No paths. No SBOM. |
| 2 | OSV snapshot sync (gadriel code policies update --osv) | osv-vulnerabilities.storage.googleapis.com (Google's OSV CDN) | A single bulk-snapshot GET of the whole OSV database. | No per-finding query. No PURLs. No dependency names. No code. |
| 3 | Optional Claude reasoning (Layer-4 confirmation, gadriel code fix) | api.anthropic.com | For the finding under review only: a redacted excerpt of ~5 graph-neighbor code chunks (typically 50–200 lines), graph metadata (numbers, not the graph), the finding body, and a 256-bit codebase fingerprint. Uses the runner's own key (e.g. ANTHROPIC_API_KEY), not the portal token. | The full file. Other files. The full graph. Git history. The SBOM. Other findings. Repo/branch names or any path outside the neighbor chunks. |
| 4 | Private policy-repo fetch (gadriel code policies update) | github.com/Gadriel-ai/gadriel-code-policies | An auth-gated git fetch/tarball pull of the GVL rule corpus. Read-only. License token sent only as the Authorization: Bearer header, not the body. | No code. No findings. No telemetry. |
| 5 | Opt-in metrics (--telemetry-enabled, default OFF) | sync.gadriel.ai | Aggregate counts (findings-by-pillar, intercept-layer counts), durations, CLI version, OS string. | Code. Paths. Finding bodies. Repo/branch names. Anything PII or workspace-identifying. |
Row 1 is mandatory but is a license gate, not a content channel — only auth material crosses the wire. Rows 3 and 5 are opt-in. Everything else — source, paths, findings, scores, SBOM, and the local audit log — stays on the machine.
The OSV mirror is bulk-sync, not per-finding query
A naive integration would query OSV once per dependency, leaking your entire dependency tree. Gadriel forbids that pattern:
gadriel code scan ──▶ reads lockfiles locally
──▶ queries the LOCAL OSV snapshot (~/.cache/gadriel/osv-snapshot/)
└─ no network call
gadriel code policies update --osv
──▶ one wholesale GET of the entire DB blob
└─ request body has no PURLs, no dep names, no code
A network observer sees one large opaque transfer to a mirror — they learn nothing about which dependencies you have. The snapshot is signature-verified locally before it is trusted.
3. The AI coding agent runs locally — it is not a SaaS endpoint
A common misreading: "the assistant makes an LLM call, so my code goes to the cloud." That is wrong on two counts:
- The AI coding agent runs in your environment. Claude Code, Cursor, Copilot, Codex, Aider — all local. None is a SaaS service you upload your repository to.
- The LLM call those agents make is the agent's call, gated by your keys, on your machine. Gadriel does not proxy it. Gadriel makes its own LLM call only at the carve-out points in row 3, sending only the redacted contract — never raw source.
The find→fix→re-validate loop, end to end, is entirely local except for the agent's own (already-existing) LLM call:
[user machine — all local]
developer / AI agent writes code
↓
gadriel scans (no outbound)
↓
finding pushed to the agent (local MCP over stdio, or file-watch)
↓
agent applies fix (its own local LLM call, its own credentials — NOT proxied)
↓
gadriel re-scans (no outbound)
↓
.security/audit-log.jsonl written (LOCAL ONLY — never sent anywhere)
4. How Tier 2 preserves privacy
Tier 2 (see tiers.md) reaches the semantic long-tail by
borrowing the LLM the host assistant is already running — over the local
MCP submit_ai_findings callback. Crucially, this creates no new egress
relationship: Gadriel does not open its own connection to send code for
Tier-2 analysis. It hands the semantic question to the assistant already in
the loop, which uses its existing, user-authenticated LLM connection.
Tier 2 is also off by default and privacy-gated:
toml[tier2]enabled = false # default OFFsource_consent = false # default OFF — required before any source excerpt is sent
source_consent defaults false and never auto-enables. It corresponds to
the persisted --send-source consent — without it, no source excerpt is
placed in a Tier-2 request. With Tier 2 off, the MCP server emits
tier2_request: null and no session is ever opened.
5. Build-time enforcement
The boundary is not just policy — it is enforced by cargo-deny in CI.
crates/code-security/deny.toml is a positive allowlist naming
the only crates permitted to depend on an HTTP/network client (the auth,
OSV-sync, policy-fetch, and Claude-reasoning crates). Any new dependency that
pulls in reqwest/hyper/tonic/etc. into a scanner crate fails
cargo deny check bans before the PR can merge:
bashcargo deny --all-features check --config crates/code-security/deny.toml bans
The check is scoped to the code-security member so it is isolated from the
Preflight runtime layer, which legitimately uses HTTP clients (a different
deployment model, a different boundary). Local persistence (the rvf store at
~/.gadriel/store/ per-machine and ./.security/store/ per-repo) is
signature-verified at rest and never crosses the boundary — it stays
local.
6. Running fully air-gapped
Gadriel works with no network at all. Government, defence, and security-sensitive customers run it this way.
-
Force offline mode:
bashexport PREFLIGHT_OFFLINE=1 -
Pre-warm the OSV snapshot — sync it once on a connected machine (or point
osv_mirror_url/GADRIEL_OSV_MIRROR_URLat an internal mirror), then carry~/.cache/gadriel/osv-snapshot/across. SCA queries are local file reads against that snapshot, so no per-finding call is ever made. -
Offline license + policies: air-gapped customers receive an offline license grant and a GVL policy snapshot bundle from Gadriel support, rather than the online portal refresh (row 1) and
git fetchpolicy update (row 4).
With those in place, all six scan types, the graph build, secret detection, scoring, the SBOM, and the report all run with zero outbound traffic. Tier 2 is simply absent (no host agent / no LLM), and analysis cleanly degrades to Tier 0 — the deterministic gate is unchanged.
Summary
- Analysis is local. Only the five enumerated calls in §2 ever go out; rows 3 and 5 are opt-in, and none of them carry raw source.
- The AI agent's LLM call is its own, on your machine, not proxied by Gadriel.
- Tier 2 adds no new egress — it reuses the host assistant's existing LLM connection, is off by default, and is source-consent gated.
- The boundary is enforced at build time by cargo-deny, and the whole product can run fully air-gapped.
