codebase-index is local-first and offline by default. Its threat model assumes the indexed
repository may contain secrets and that a skill must not exfiltrate code or run dangerous commands.
Trust model in 60 seconds
- Offline by default — the base install has zero network dependencies; nothing leaves your machine (§1, §4).
- One opt-in exit, triple-gated — external embeddings require
allow_externaland an env API key and a printed endpoint warning, or they are refused (§4).- Secrets never get in —
.env, keys, certs, and credential files are excluded before parsing (§2).- Secrets never get out — every snippet is redacted before it reaches the agent (§3).
- No telemetry, ever — no analytics, no phone-home, no usage data.
- Verify it yourself —
codebase-index doctor --strictaudits all of the above and gates CI (§6).The same callout appears in the README so the trust story is identical wherever a reader lands.
- Local-first — index, query, and storage all happen on the user's machine.
- No network by default — the base install has no network dependency. The only code path that can leave the machine is an external embedding API, which is opt-in and off by default.
- Never index sensitive material — secrets,
.env, keys, certs, build/dependency/binary/ generated/huge files are excluded before parsing. - Redact secrets in output — even indexed snippets are scrubbed before being shown to Claude.
- Respect ignore files —
.gitignore,.cursorignore,.claudeignore,.codeindexignore. - Minimal, safe tool surface — the skill's
allowed-toolsonly permits the read-only CLI subcommands and read-only fallbacks (ripgrep/Grep/Glob). Noclean, no arbitrary shell. - Workspace trust — indexing executes parsers over repo content; treat indexing an untrusted
repo as you would opening it in an editor.
doctorwarns before risky operations.
A file must pass every gate to be indexed:
| Gate | Rule |
|---|---|
| Ignore files | Not matched by .gitignore / .cursorignore / .claudeignore / .codeindexignore |
| Built-in denylist | Not in node_modules, .venv, dist, build, target, .git, vendor, __pycache__, etc. |
| Secret filenames | Not .env*, *.pem, *.key, *.p12, *.pfx, id_rsa*, *.crt, *.keystore, credentials*, secrets* |
| Binary | No NUL bytes / not a known binary extension (images, archives, fonts, compiled artifacts) |
| Size | size_bytes <= max_file_bytes (default 1 MB) |
| Generated | Not matched by generated-file patterns (*.min.js, *.lock, *.pb.go, *_pb2.py, *.generated.*) — indexed as summary-only at most |
.codeindexignore is the tool's own ignore file (highest specificity) so users can exclude
paths from indexing without affecting git or other tools.
Two layers:
- At index time — files that look like secret stores are excluded entirely (above).
- At output time — every snippet is passed through a redactor before emission. Patterns:
- high-entropy strings assigned to keys named
*key*,*secret*,*token*,*password*,*api* - common formats: AWS keys (
AKIA...), private key headers (-----BEGIN ... PRIVATE KEY-----), JWTs, bearer tokens, connection strings with credentials,xox[baprs]-Slack tokens. - Matches are replaced with
«redacted:<type>», preserving line numbers.
- high-entropy strings assigned to keys named
Redaction is conservative: it never widens the snippet, only masks within it.
- Default:
embeddings.backend = "noop"(disabled) — pure lexical+symbol+graph search. embeddings.backend = "local"→ on-device model (e.g. sentence-transformers). Still no network at query time (model downloaded once at setup, which the user initiates explicitly).embeddings.backend = "external"→ sends chunk text to a configured API. This requires:- explicit
embeddings.allow_external = truein config, and - an env-provided API key, and
doctorandindexboth print a clear warning naming the endpoint. Without all three, external embedding is refused.
- explicit
SKILL.md declares a narrow allowed-tools:
allowed-tools:
- Bash(codebase-index search:*)
- Bash(codebase-index explain:*)
- Bash(codebase-index symbol:*)
- Bash(codebase-index refs:*)
- Bash(codebase-index impact:*)
- Bash(codebase-index diff-impact:*)
- Bash(codebase-index architecture:*)
- Bash(codebase-index path:*)
- Bash(codebase-index describe:*)
- Bash(codebase-index graph:*)
- Bash(codebase-index stats:*)
- Bash(codebase-index doctor:*)
- Bash(codebase-index update:*)
- Bash(codebase-index index:*)
- Grep
- GlobExplicitly not allowed via the skill: clean, init, watch, an unscoped
codebase-index *, python -m codebase_index *, or unscoped Bash.
Destructive or scaffolding actions remain a human/manual decision. The wrapper
scripts (scripts/cbx) only forward to the installed codebase-index binary
and reject unknown subcommands.
codebase-index doctor (and --strict for CI) reports:
- whether the cache is inside
.gitignore(warns if the index could be committed) - whether external embeddings are enabled and to which endpoint
- any indexed file that matches a secret pattern (should be none → flags a leak)
- ignore-file coverage and any oversized/binary files that slipped through
- the resolved
allowed-toolsvs. the recommended minimal set - world-writable cache directory permissions
doctor exits non-zero under --strict if any high-severity finding is present, so it can gate CI.
- Must not run
codebase-index index --rebuildautomatically on huge/untrusted repos without the freshness check indicating it's needed. - Must not echo raw
.env/secret file contents even if a user pastes a path — the CLI refuses to read excluded files for snippet output. - Must not enable embeddings or any network path on its own.