Read this document in Portuguese (pt-BR).
- The table below lists which sqlite-graphrag versions currently receive security patches
- Users on deprecated lines are STRONGLY encouraged to upgrade to a supported release
- Upgrading early reduces exposure window and aligns with the coordinated disclosure policy
| Version | Status | Security Patches |
|---|---|---|
| 1.2.x | Supported | Yes, receives fixes (current line; latest v1.2.7 / crate 1.2.7) |
| 1.1.x | Supported | Yes, receives critical fixes; upgrade to 1.2.x recommended |
| 1.0.x | Supported | Yes, receives critical fixes; upgrade to 1.2.x recommended |
| 0.x | Unsupported | No patches provided |
- Database provenance is now published. Every side-effecting verb emits
db_path_sourceanddb_path_resolvedin the envelope, so the caller can see which database a write actually landed in - Omitting
--dbdoes NOT use the current directory: the call silently falls back to the XDG database and exits 0. The provenance fields make that previously silent write to the wrong database visible
- An unknown key in
--selector--filteris no longer silenced. Such a request used to degrade into an empty result set with exit 0; it is now rejected with exit 2 - A
--filternow evaluates the whole set instead of only the current page - A declared knob with no effect no longer returns exit 0
- Security consequence: a caller can no longer mistake a malformed request for a legitimate empty result
- Failure envelopes are never filtered. The agent-native output surface (
--filter,--select,--max-items, …) reshapes result rows only; an envelope carryingerror: trueorok: falsereaches the caller verbatim. A caller can therefore never be led to read a failure as an empty-but-successful result set - Truncation is never silent.
--truncate-contentand--max-output-bytesrecord what they removed underagent_surfaceand raise the top-leveltruncatedflag;--max-output-bytesdrops trailing elements rather than slicing JSON text, so a capped envelope still parses --no-inputrefuses stdin declaratively. Every stdin reader fails up front with exit 65 instead of blocking, so an unattended invocation cannot hang waiting for input that will never arrive. Precedence: flag > XDGcli.no_input>false- A malformed
--filterexits 2 rather than returning zero rows, so a typo is never mistaken for a legitimate empty result - Schema stays at v16 (no main-DB migration); the surface is additive and opt-in
- Namespace claim isolation on the enrich sidecar:
dequeue_next_pending/count_eligible_pending/ resume requireoperationandnamespace, so a drain in one namespace cannot claim or process pending work from another (reduces cross-namespace processing risk and circuit-breaker fallout) - Schema stays at v16 (no main-DB migration); changes are sidecar queue behaviour only
- No product env (
SQLITE_GRAPHRAG_*) on the hot path; runtime config is flag > XDGconfig set> default DEFAULT_EMBEDDING_DIM=1024(override via--embedding-dim/ XDGembedding.dim; existing DBs keepschema_meta.dim)- GAP-SG-139: host surfaces accept
--dbas a documented no-op (config,slots,cache,completions); graph surfaces unchanged - Offline quality gate:
scripts/e2e_offline_v120.sh
- v1.1.05 introduces
deep-research --output PATH, which writes the full JSON envelope through the atomwrite algorithm (tempfile → fsync → rename) and emits a short ack on stdout carrying ablake3checksum - The I/O contract holds: JSON on stdout, logs on stderr. Never redirect both into the same file with
&>— that contaminates the JSON parse - The global
--quiet/-qflag suppresses non-error tracing on stderr, cutting noise in agent pipelines without altering the envelope - Prefer
link --from-id/--to-idwhen the identity is a numeric entity ID; purely numeric names are rejected byvalidate_entity_name(v1.1.05) so--create-missingcannot create phantom entities - Self-referential merges in
merge-entities(target ID/name also listed as a source) are rejected BEFORE any database work (v1.1.05), protecting graph integrity against shell word-splitting - The shared helpers in
src/atomic_io.rs(write_atomic,write_json_atomic) are unit-tested; thetests/v1105_incident_bugs_regression.rssuite covers the CLI path - Output-file integrity: check the
blake3checksum from the stdout ack against the written content when the pipeline needs end-to-end verification
- OBRIGATÓRIO report security issues through GitHub Security Advisories in the public
sqlite-graphragrepository as the preferred private channel - Use email at daniloaguiarbr@proton.me only as fallback when GitHub private reporting is unavailable
- JAMAIS open a public GitHub issue, pull request, or discussion for security-related reports
- Include a minimal reproduction, affected versions, and expected versus actual behavior
- Include your environment details such as OS, architecture, and rustc version
- Include CVSS 3.1 severity estimate when possible to accelerate triage
- Triage of every advisory is committed to start within 72 business hours of submission
- Initial acknowledgment email will be sent within that same 72-hour window
- You will receive a case identifier and an assigned maintainer contact
- Progress updates are shared at minimum every 7 days until resolution or public disclosure
- Critical severity (CVSS 9.0 to 10.0) receives a patch within 7 calendar days of validated triage
- High severity (CVSS 7.0 to 8.9) receives a patch within 14 calendar days of validated triage
- Medium severity (CVSS 4.0 to 6.9) receives a patch within 30 calendar days of validated triage
- Low severity (CVSS 0.1 to 3.9) receives a patch within 90 calendar days of validated triage
- Released fixes follow immediately with a CHANGELOG entry and a GitHub Security Advisory when the affected line is still supported
- We follow coordinated disclosure with a standard 90-day embargo window from initial report
- The embargo can be shortened when a fix is released earlier than 90 days
- The embargo can be extended when a fix demands more time and the reporter agrees
- Public disclosure includes a CVE identifier when the impact warrants one
- Public disclosure includes the GitHub Security Advisory with affected versions and patched version
- Credit is attributed to the reporter unless anonymity is explicitly requested
- Patches for supported versions ship as a new patch release on crates.io and GitHub Releases
- Every release is validated with the full 10-command quality gate described in CONTRIBUTING
- Advisory and license checks run LOCALLY by the operator with
cargo auditandcargo deny check advisories licenses bans sources; the repository has no CI workflows, andcargo testis the only automatic gate - Supply chain is enforced via pinned
constant_time_eq = "=0.4.2"to protect MSRV 1.88 - Transitive dependency MSRV drift is monitored proactively per PRD policy
- CURRENT SURFACE, measured on the 1.2.8 binary of this working tree: NOTHING in
src/starts a subprocess. Embedding and enrichment are HTTPS calls to OpenRouter, made in-process and one shot, selected by--embedding-backend openrouter|autoand--llm-backend openrouter|none. - CURRENT CREDENTIAL PATH:
config add-key --provider openrouter --from-stdinstores the key at rest under XDG and keeps it out of shell history and out of the process table;--openrouter-api-key <KEY>overrides that store for a single invocation and IS visible in the process table, so prefer the store.config list-keysshows masked fingerprints,config remove-keydeletes one, andconfig doctorreports which layer resolved the key. Product environment variables are not read at runtime and are not a configuration channel. - CURRENT RESIDUAL RISK, stated without invention: the key sits in an XDG file protected by filesystem permissions, and it is transmitted to the provider over TLS. There is no OAuth-only guard, no env-clear whitelist and no spawn boundary in this build, because there is no child process — the protections described in the rest of this section are NOT in force and must not be relied upon.
- EVERY BULLET BELOW IS HISTORICAL. It records the subprocess era, removed in v1.2.0, and is kept as a register of what was built rather than as a guarantee. Verification:
ANTHROPIC_API_KEYandOPENAI_API_KEYoccur zero times insrc/, andclaude_runner.rs,codex_spawn.rsandingest_claude.rsare not files of this repository any more. - HISTORICAL: the default build was LLM-only and one-shot, and every embedding call spawned a headless
claude codeorcodexsubprocess. - HISTORICAL: the spawn ABORTED with
AppError::Validationand exit code 1 whenANTHROPIC_API_KEYorOPENAI_API_KEYwas detected in the environment. No such abort exists today, and no code reads either variable. - HISTORICAL: the OAuth flow (Claude Pro/Max or ChatGPT Pro subscription) was the ONLY accepted credential mechanism. Today the accepted mechanism is an OpenRouter API key held at rest under XDG.
- HISTORICAL: both API-key env vars were INTENTIONALLY ABSENT from the env-clear whitelist in
claude_runner.rs,codex_spawn.rsandingest_claude.rs, as defence in depth against a future refactor moving the guard. That defence disappeared together with the risk it covered: no child is started, so no variable can reach one. - HISTORICAL: the
--bareflag (which would also demand an API key) was REMOVED from every executable path in v1.0.69. - HISTORICAL: four
#[serial_test::serial(env)]tests validated the canonical flag set and the abort behaviour. - HISTORICAL RATIONALE:
docs/decisions/adr-0011-oauth-only-enforcement.mdfor the full reasoning anddocs/decisions/adr-0025-oauth-only-embedding.mdfor the v1.0.76 embedding-specific application. - Migration: an operator still relying on
ANTHROPIC_API_KEYorOPENAI_API_KEYmust move to an OpenRouter key, because since v1.2.0 neither variable is read by anything.
- HISTORICAL: the default build PRESERVED seven custom-provider env vars when spawning
claude -porcodex execsubprocesses, enabling Anthropic-compatible providers (MiniMax/api.minimax.io, OpenRouter, AWS Bedrock, corporate gateways) - The preserved vars are
ANTHROPIC_AUTH_TOKEN,ANTHROPIC_BASE_URL,OPENAI_BASE_URL,CODEX_ACCESS_TOKEN,CLAUDE_CODE_ENTRYPOINT,DISABLE_TELEMETRY, andOTEL_EXPORTER_OTLP_ENDPOINT - HISTORICAL: those vars were SEMANTICALLY DISTINCT from the OAuth-only-rejected
ANTHROPIC_API_KEYandOPENAI_API_KEY, and the OAuth-only guard rejected the API keys with exit 1. Measured on this working tree:claude_runner.rs,codex_spawn.rsandingest_claude.rsare not files of this repository any more, and no guard runs - HISTORICAL: the whitelist lived in a shared helper
src/spawn/env_whitelist.rsexposingapply_env_whitelist(cmd, strict)andis_strict_env_clear(), and the three spawners delegated to it. The wholesrc/spawn/directory was removed with the subprocess backends - HISTORICAL: for compliance environments that forbid credential forwarding via env vars (PCI-DSS, SOC2, HIPAA), operators passed --strict-env-clear, and strict mode preserved only
PATHwhile dropping every other env var. The flag lived on the global surface until v1.2.2, assrc/cli/globals.rsstill records; a 1.2.8 binary answers unexpected argument '--strict-env-clear' found with exit 2. Nothing forwards a credential today, because nothing spawns. Product envSQLITE_GRAPHRAG_*is not read at runtime, so it is not the configuration path either - HISTORICAL: five
#[serial_test::serial(env)]regression tests lived intests/claude_runner_env.rs, a file this repository no longer contains, covering custom-provider propagation, OAuth-only abort preservation, codex base-URL inheritance, strict-mode credential dropping, and a no-leak audit that scans subprocess stderr for the literal token value withRUST_LOG=trace - No telemetry is emitted anywhere in this product, then or now. HISTORICAL: the fix was silent unless the OAuth-only guard fired, surfacing an orientative marker arg pointing to
ANTHROPIC_AUTH_TOKENor~/.codex/auth.jsonas legitimate resolutions; that guard no longer exists - HISTORICAL threat model: credential values for custom providers flowed from the orchestrator process to the LLM subprocess over the process boundary, the audit-of-no-leak test guarded against a
tracingmacro printing the raw token to stderr, and operators on shared hosts were told to prefer --strict-env-clear. That boundary does not exist in v1.2.8 and the flag is rejected with exit 2; the credential surface is now a single OpenRouter key read from the XDG store and sent over TLS - See
docs/decisions/adr-0041-preserve-custom-provider-env.md(EN) and.pt-BR.mdfor the full architectural decision and the alternatives considered
- Every LLM subprocess spawn passes through src/spawn/preflight.rs (15 unit tests, 7 guards) BEFORE the fork. Failures return AppError::PreFlightFailed (exit code 16, EX_CONFIG).
- 7 guards: check_argv_size, check_binary_exists, check_mcp_config_inline (replaces literal --mcp-config '{}' with tempfile, fixes BUG-2), check_mcp_config_path, check_walkup_mcp_json (validates .mcp.json walk-up, fixes BUG-5), check_output_buffer (fixes BUG-4), check_claude_config_dir (avoids MCP bleed-through).
- Bypass (emergencies only):
sqlite-graphrag config set spawn.skip_preflight 1disables all 7 guards. Last-resort opt-out; bypassing reverts to direct Command::spawn() and inherits all 5 BUG classes. Product env is not the supported path in v1.1.8. - v1.0.88 hotfixes: BUG-11 (preflight failure in extract/llm_embedding.rs did not propagate to remember; fixed with embed_via_backend_strict), BUG-12 (OAuth-only emitted 2 identical stderr lines; fixed with single-line stderr), BUG-13 (link --create-missing bypassed entity-name validation; fixed by validating BEFORE normalizing in entity_validation_integration.rs, 8 tests, 4-char boundary).
- See docs/decisions/adr-0045-preflight-validation-layer.md and adr-0046-preflight-remediation.md for the full architectural decision.
- BUG-YES-FLAG-IGNORED: three destructive commands (slots release, purge, cleanup-orphans) declared --yes but executed deletions without it. All now abort with AppError::Validation when --yes is absent, matching the 5 other destructive commands that already enforced this
- BUG-BOOLISH-ENV: four boolean CLI flags (--skip-embedding-on-failure, --strict-env-clear, --dry-run-backend, --llm-slot-no-wait) rejected standard Unix env values (1, yes, on) with exit 2. Fixed via BoolishValueParser. The
SQLITE_GRAPHRAG_SKIP_EMBEDDING_ON_FAILUREform is historical and was removed; pass the flag value on the command line instead - BUG-STRICT-ENV-PROPAGATION (HISTORICAL): the --strict-env-clear CLI flag was silently ignored because main.rs did not propagate it to the env var. Fixed at the time via set_var before command dispatch; the flag itself was removed after v1.2.2 and a 1.2.8 binary rejects it with exit 2
- GAP-FLAGS-MORTAS: 7 global LLM flags were accepted by clap but silently ignored because internal modules read env vars directly. Fixed: main.rs now bridges CLI flags to env vars via set_var
- GAP-RECALL-001: embedding deadlock from stale LLM subprocess slots resolved via explicit drop(stdin), reduced timeout (300s to 30s), stale slot reaper, and sqlite-graphrag orphan process cleanup
- See docs/decisions/adr-0050-embedding-deadlock-remediation.md for the full architectural decision
- v1.0.93 introduces
--embedding-backend openrouterwhich uses a real API key (NOT OAuth) for direct REST API calls to OpenRouter - The API key is provided via
--openrouter-api-keyflag or XDGconfig add-key openrouter(OPENROUTER_API_KEYis ignored at runtime; G-T-XDG-04) - The key is wrapped in
secrecy::SecretStringand zeroized on drop — NEVER held as plain String in memory after initialization - The key is NEVER logged to stderr even at
RUST_LOG=tracelevel - The key is NEVER persisted in
graphrag.sqliteor any cache file - The key is NEVER forwarded to LLM subprocesses (claude, codex, opencode) — it flows only to
reqwestHTTPS calls toapi.openrouter.ai - This is SEMANTICALLY DISTINCT from the OAuth-only enforcement on LLM backends:
ANTHROPIC_API_KEYandOPENAI_API_KEYstill ABORT with exit 1 - Historical note:
OPENROUTER_API_KEYwas never in the env-clear whitelist; the product never reads that env var now — use flag orconfig add-keyonly - Operators on shared hosts SHOULD prefer
config add-key(file mode 0600) or the CLI flag; product env is not a supported channel - See
docs/decisions/adr-0052-openrouter-embedding-backend.mdfor the full architectural decision
- We publicly acknowledge researchers who report vulnerabilities responsibly
- This section is open to contributions: your name will be added after coordinated disclosure
- If you prefer anonymity, we honor that preference without exception
- SEMPRE install published releases with
cargo install sqlite-graphrag --locked - Use
cargo install --path .only when testing an unreleased local checkout intentionally - SEMPRE rotate your
crates.ioAPI tokens on a regular schedule - SEMPRE keep your rustc toolchain updated to the latest stable release compatible with MSRV 1.88
- SEMPRE review CHANGELOG entries before upgrading across major versions
- JAMAIS commit secrets or tokens to the repository or to derived forks
- JAMAIS disable the memory guard in production via undocumented flags
- JAMAIS raise heavy-command concurrency blindly on memory-constrained hosts; prefer serial execution during audits
- JAMAIS bypass
cargo auditwarnings without opening a tracked security advisory - HISTORICAL advice, kept as a record: JAMAIS set
ANTHROPIC_API_KEYorOPENAI_API_KEYin the environment, because the spawn would abort with exit 1. A 1.2.8 binary reads neither variable and spawns nothing, so setting them changes nothing — and removing them protects nothing. - HISTORICAL advice: JAMAIS rely on
ANTHROPIC_AUTH_TOKENforwarding when the host is shared with untrusted processes, preferring --strict-env-clear so credentials stayed in the parent process only. Both the forwarding and the flag are gone; on a shared host the exposure to watch today is--openrouter-api-keyon the command line, visible in the process table — store the key withconfig add-key --provider openrouter --from-stdininstead. - Prefer
link --from-id/--to-idover name-based linking when the identity is an integer entity ID; pure-numeric entity names are rejected byvalidate_entity_name(v1.1.05) so--create-missingcannot create ghost numeric entities - Self-referential
merge-entities(target ID/name also listed as a source) is rejected BEFORE any DB work (v1.1.05), protecting graph integrity against shell word-splitting mistakes - v1.1.06: first-scan wall-clock for
enrich --operation entity-connect/cross-domain-bridgesusesInterruptHandleand returns Timeout exit 1 (not singleton exit 75). Orchestrators must not treat scan timeout as a lock contention signal. See ADR-0066 anddocs/HEADLESS_INVOCATION.md.
- v1.0.94 makes
enrich --modeREQUIRED (removed theclaude-codedefault); omitting it is rejected by clap with exit 2. - This prevents an accidental
claude -pspawn that would inherit the caller project.mcp.jsonand execute untrusted MCP servers in a headless context. - No new exit code and no new environment variable are introduced; the change is a safer default surface only.
- Valid modes are
claude-code,codex,opencode; pick the one matching your--llm-backend.
- v1.0.95 adds
enrich --mode openrouter, which routes the JUDGE step to OpenRouter/chat/completionsover HTTPS (src/chat_api.rs) instead of spawning a local CLI. - It reuses the SAME OpenRouter credential already documented for the embedding backend (flag /
config add-key;OPENROUTER_API_KEYis ignored at runtime), with the SAME handling: wrapped insecrecy::SecretBox, zeroized on drop, NEVER logged, NEVER passed to any subprocess. - The key flows only into the
reqwestHTTPS client targetingopenrouter.ai; it is not in the env-clear whitelist and stays in the parent process only. - No new credential surface is introduced beyond what is already documented for the OpenRouter embedding backend.
- v1.0.96 adds bounded concurrency to the OpenRouter embedding REST path (
embed_passages_parallel_with_embedding_choice); in-flight requests are clamped to 1..=16 (default 8, a Cloudflare-safe range) via atokio::task::JoinSet, with NO new dependency. - The fan-out only parallelizes outbound HTTPS reads to
openrouter.ai; it does NOT widen the credential or network surface, and the SAME OpenRouter key handling (flag/config add-key, secrecy/zeroize, never logged, never passed to a subprocess; product env ignored) applies unchanged. - SQLite writes stay serialized via WAL plus atomic claim — the database remains single-writer; the dead-letter queue (
error_class,next_retry_at, terminaldead) only schedules retries and never bypasses the writer lock. enrich --statusis read-only: it inspects the queue without an LLM call and without acquiring the singleton, so it is safe to wire into hooks and timers.- No new exit code and no new credential surface are introduced.