Skip to content

Security: simerugby/cartographer

Security

docs/SECURITY.md

Security & cost posture

Cartographer is an honesty-branded tool, so its own engineering has to clear the same bar it sets for everything else. (SPEC §7.)

STRIDE threat model (D4)

One pass over the six STRIDE categories. Each row points at a mitigation that already lives in the codebase; the sections below carry the detail.

Threat (STRIDE) Cartographer's mitigation
Spoofing Authenticated Cloud Run (--no-allow-unauthenticated, reached via IAP / an identity token); a leaked URL cannot act as a caller.
Tampering The deterministic AST gate (grounding/gate.py) re-verifies every claim's evidence tokens against real source on each run; all ingested PR / comment / docstring text is treated as data, never as a verdict. Pinned deps + lockfiles resist dependency tampering.
Repudiation Per-token receipts (Verdict.checked) on every ACCEPT/REJECT, an eval gate re-derivable in CI, and "as audited on <commit>" provenance stamped on the numbers. Every claim is reproducible, not asserted.
Information disclosure Contributor identities pseudonymized (SHA-256 → keeper-XXXX); the live-path caller identity is hashed, never stored or logged; keyless Vertex ADC, no API keys in code / prompts / logs; .gitignore + gitleaks / detect-secrets.
Denial of service / denial-of-wallet Input length cap (MAX_QUESTION_CHARS) rejected before any provider call, a per-identity rate limit (HTTP 429), a hard hybrid live-call budget (MAX_LIVE_CALLS), and a cached + scale-to-zero deploy (--max-instances 3) with a billing-budget alert. Detail below.
Elevation of privilege Runtime service account scoped to roles/aiplatform.user only; the deploy creates no IAM / SA / VPC (it reuses existing resources, least privilege); the verdict carries no model, so a prompt cannot escalate into an accepted fabrication.

Denial-of-wallet (the judged path can't run up a bill)

The judged path never touches a live LLM. For the curated questions the demo serves pre-baked, cached answers (CARTOGRAPHER_LLM_MODE=cached, the default), and the cached provider refuses on a miss instead of falling through to a live call. A live Gemini call happens only when an operator explicitly sets CARTOGRAPHER_LLM_MODE=live with ADC present.

The service is authenticated rather than public: Cloud Run deploys with --no-allow-unauthenticated and is reached via gcloud run services proxy or an identity token, so a leaked link cannot drive spend. Two backstops sit underneath that. A hard instance cap (--max-instances 3) with scale-to-zero (--min-instances 0), and a billing budget with an alert on the GCP project, set in the console.

Supply chain

  • Dependencies are pinned across requirements.txt, requirements-dev.txt, and web/package-lock.json.
  • pip-audit (Python advisories) and gitleaks (secret scan) run in CI on every push and PR.
  • There is no LiteLLM in the stack. Gemini is native in ADK, so the litellm 1.82.7/1.82.8 supply-chain advisory doesn't apply by construction.

Secrets

Gemini runs keyless through Vertex ADC, on a runtime service account scoped to roles/aiplatform.user and nothing else. No API keys live in code, prompts, logs, or commits. .gitignore blocks .env, *.key, and service-account*.json, and detect-secrets / gitleaks enforce it.

Spotlighting (prompt-injection containment, D4)

Every piece of ingested text (repo, PR, issue, git history) is treated as data, not as instructions. The agent's decisions come from the deterministic AST gate (grounding/gate.py), never from text in the corpus.

The verifier has two layers. The deterministic gate is authoritative for rejection, while the Gemini LLM-judge is advisory only: it can downgrade or reject a claim but can never accept one on its own. A refusal is the safe default.

Quality gate

bash scripts/verify.sh blocks merge. It runs ruff, the full test suite, and the eval gate (0 fabrications by the deterministic gate, refusal pass^k, answer-coverage, gap precision/recall, and the web build).

There aren't any published security advisories