Skip to content

Security (technical index)

This file is a house-pipeline-convention index into Odysseus' existing, more thorough security documentation - it does not replace it:

  • SECURITY.md - deployment guidance, the "publishing a fork" pre-push checklist (including its own secret grep), and how to report vulnerabilities.
  • THREAT_MODEL.md - trust boundary, role/capability matrix, and what the threat model does and does not try to prevent.
  • docs/security-ci.md - what each security-focused CI job checks (gitleaks, zizmor/actionlint, hadolint, Trivy, dependency review) and why.

Secret handling

  • Real secrets (API keys, session/auth data, the SQLite DB) live under data/ and .env, both gitignored (see .gitignore lines for .env and data/) and never committed.
  • Third-party integration credentials configured through the UI (src/integrations.py) are encrypted at rest (src/secret_storage.py), decrypted only for the outbound request, masked in API responses (mask_integration_secret), and the store file is written 0600.
  • .github/workflows/secret-scan.yml runs gitleaks (pinned binary, checksum-verified) against the full history on every push/PR.

2026-07-08 house-pipeline audit results

A secret pre-scan flagged 24 pattern hits, all inside tests/ plus three api_key-adjacent lines in src/integrations.py. Full classification:

  • src/integrations.py - every hit is the api_key variable/dict-key name (e.g. integration.setdefault("api_key", ""), headers[header_name] = api_key), never a literal secret value. False positive (parameter name, not a secret).
  • tests/* - every hit is a short, non-entropy fixture string used to exercise auth/owner-scope/masking/encryption code paths ("sk-secret", "fake-key", "old-password", "secret-key", "SUPER_SECRET", etc.), or the deliberately-named tests/test_code_nav_tools.py ("GREPSECRET_TOKEN_ZZZ") canary. Fixture - none match a real provider's key format (length/entropy), and gitleaks already passes this repo's CI with these fixtures present. Seven fixture strings of the form Bearer <12+ token chars> (e.g. "Bearer live-access-token") matched the house pipeline's stricter gate regex despite being semantically fake, and were shortened ("Bearer live-tok" etc.) so scanners never need a judgment call; one documentation string in src/integrations.py ('Bearer ACCESS_TOKEN') became 'Bearer <ACCESS_TOKEN>' for the same reason. All affected tests still pass.
  • No real credential was found anywhere in tracked source. .env (present on disk, not tracked) contains only local Ollama host/port config.

A Codex security review additionally flagged a HIGH-severity SSRF/credential- exfiltration concern in src/integrations.py's path-join logic (execute_api_call / _join_integration_url), which on verification turned out to be a false positive - _join_integration_url's path.lstrip("/") already keeps the joined URL on the configured host. An explicit guard (reject path starting with //, plus a post-join host/scheme/port assert) was added anyway as defense in depth, with regression tests in tests/test_integration_api_call_ssrf.py, so the invariant no longer depends on lstrip's exact behavior.

Codex also flagged unpinned Python dependencies in CI (pip install -r requirements.txt with floating package names) as a MEDIUM supply-chain gap. Not fixed in this pass (hash-pinning ~50 requirements and validating the app still works is a larger change than an audit gap-fix); tracked in docs/BOARD.md.