# Security model

What the kit touches, what it never touches, and the mitigations baked in.

---

## Threat surface overview

| Risk | Where it lives | Mitigation |
|---|---|---|
| Memory-repo leaks session content | Sleep-sync pushes trace JSONL off-machine | Private repo enforced by wizard; `[SENSITIVE-IP]` sessions skip push entirely |
| Hardcoded tokens in source | Edits by agents / humans | `secrets-guard` Rust hook (PreToolUse Edit\|Write) blocks known token shapes |
| Accidental push of flagged content | `git push` command | Optional `git-push-guard` + `leak-matrix` pre-commit hooks (off by default; opt-in for users with sensitive private code) |
| Malicious GitHub Action tag re-point | `.github/workflows/*.yml` | SHA-pinning + `validate-workflow-shas.sh` + `actionlint` in CI |
| S3 SSRF / IMDS credential exfil | `kei-store` with custom endpoint | `validate_endpoint` rejects loopback / link-local / metadata hosts |
| Escape-sequence injection via brain name | `keisei status` / `attach` output | Control-byte sanitiser on every manifest-sourced string |
| Brain → `$HOME` pivot via symlink | `keisei attach <USB>` | Brain root rejected if symlink; `mcp_server` path must be relative + inside brain |
| SQLite WAL corruption on USB mount | `keisei mount <exFAT drive>` | Runtime advisory; exFAT/FAT32 warning in USB guide |

## Key mitigations in detail

### Memory-repo should be private (your choice)

Sleep-sync pushes your session traces (prompts, tool calls, file paths, code snippets) to a git repo you control. `/sleep-setup` Phase 1 warns loudly on PUBLIC visibility. A public memory-repo leaks everything your agents have seen.

For users with sensitive private code: tag a session with `[FLAG-LOCAL]` in the prompt (or run from a CWD listed in `~/.claude/skip-sync-paths`), and `session-end-dump.sh` skips the push entirely — local trace is kept, never leaves the machine.

### Optional: pre-commit guard for users with private-IP

If your project has unpublished patents, confidential material, or otherwise-flagged content, kit ships `leak-matrix` (regex SSoT scanner) and an optional `git-push-guard` pre-commit hook template. Both are **off by default** — public-source projects don't need them.

To enable: copy `_templates/leak-guard.sh.tmpl` → your repo's `.git/hooks/pre-commit`, edit the term list, `chmod +x`. The hook scans staged files against your patterns and refuses commits that match. Override for a specific commit: set env `LEAK_GUARD_BYPASS=1` + document the bypass reason in the commit body.

### Secrets by reference only

`secrets-guard` Rust hook blocks hardcoded tokens at `PreToolUse(Edit|Write)`. Every SSH key, API key, deploy token lives in `~/.claude/secrets/.env` (chmod 600, gitignored) or per-project `secrets/*.env`.

Hook detects these token shapes:

| Pattern | Source |
|---|---|
| `sk-[A-Za-z0-9]{20+}` | OpenAI/Anthropic legacy |
| `sk-ant-[A-Za-z0-9_-]{40+}` | Anthropic current |
| `ghp_[A-Za-z0-9]{36}` | GitHub classic PAT |
| `github_pat_[A-Za-z0-9_]{82}` | GitHub fine-grained |
| `xoxb-[0-9]+-[0-9]+-[A-Za-z0-9]+` | Slack bot |
| `[0-9]{8,10}:[A-Za-z0-9_-]{35}` | Telegram bot |
| `AKIA[A-Z0-9]{16}` | AWS access key |
| `-----BEGIN (RSA \|EC \|OPENSSH )?PRIVATE KEY-----` | PEM private keys |
| `Bearer [A-Za-z0-9._-]{20+}` | generic bearer |

Allowlist (no false-positives): env references (`$VAR`, `os.environ[...]`, `std::env::var(...)`), placeholders (`YOUR_TOKEN_HERE`, `<redacted>`), safe paths (`*/secrets/**`, `*.env.example`).

Bypass for emergency: set env `SECRETS_GUARD_BYPASS=1` on the single call.

### Supply-chain defences

All GitHub Actions in `.github/workflows/` are pinned by full commit SHA (defends against CVE-2025-30066-class mutable-tag attacks).

- `scripts/validate-workflow-shas.sh` verifies every pin exists upstream via `git ls-remote`
- `scripts/install-actionlint.sh` checks SHA-256 of the downloaded tarball before extraction
- `scripts/lint-workflows.sh` runs `actionlint` over every workflow file
- CI job `workflow-lint` runs all three on every push + PR (< 30 s)
- `dependabot.yml` raises weekly PRs for SHA updates across github-actions, npm, and cargo ecosystems

### S3 / R2 / MinIO hardening

`kei-store::s3_cloud::validate_endpoint` rejects loopback, link-local, and cloud-metadata hosts by default to close the SSRF / IMDS-credential-leak surface:

- `127.0.0.0/8`, `::1` (loopback)
- `169.254.0.0/16`, `fe80::/10` (link-local)
- `metadata.google.internal`, `metadata.aws.internal` (cloud metadata)

Plain HTTP requires opt-in via `KEI_STORE_S3_ALLOW_INSECURE=1`. When a custom (non-AWS) endpoint is set, explicit `access_key_env` + `secret_key_env` are REQUIRED — the AWS default credential chain is not consulted for non-AWS endpoints (closes the "IMDS credentials leaked to unrelated endpoint" path).

### Brain attach-marker is owner-only

`~/.keisei/attached.toml` is `chmod 0o600` on unix (Windows unchanged — no equivalent bit). Every manifest-sourced string printed by `keisei status` / `attach` / `mount` / `detach` is scrubbed through `display::sanitize_display`, which replaces every ASCII control byte (`< 0x20` or `== 0x7F`) with `?`. Closes the escape-sequence-injection surface from a malicious `brain.name` like `"evil\x1b[2Jpayload"` that would otherwise clear the user's terminal or rewrite already-printed lines.

`manifest.toml` is capped at 64 KiB — `fs::metadata` check runs before `read_to_string` so an attacker-supplied 1 GB file can't exhaust memory inside the TOML parser.

### Brain path & name validation

- Brain `mcp_server` path MUST be relative + inside the brain root (rejects `/usr/bin/curl`, `../../etc/shadow`, Windows-style `..\..\`)
- Brain `name` matches `^[a-z][a-z0-9_-]{0,63}$`
- Brain root rejected if it's a symlink (blocks USB → `$HOME` pivot)
- Adapters refuse to clobber existing `mcpServers.<name>` entries — explicit `NameConflict` error, no silent overwrite
- All config writes go through `fsx::write_atomic_json` (Windows-safe via `tempfile::NamedTempFile::persist`)

### exFAT / FAT32 warning

SQLite WAL shared-memory mmap is unreliable on those filesystems; `keisei mount` (multi-client) WILL corrupt `kei-memory` / `kei-artifact` / `kei-social-store` DBs. Brain load prints an advisory when exFAT/FAT32 is detected via `statfs(2)`. Single-client `keisei attach` on exFAT stays supported.

See [USB-BRAIN-GUIDE-macos.md](./USB-BRAIN-GUIDE-macos.md) / [-linux.md](./USB-BRAIN-GUIDE-linux.md) / [-windows.md](./USB-BRAIN-GUIDE-windows.md) for APFS / ext4 / NTFS-native walkthroughs.

## Battle-test matrix

Install-test battle matrix runs every profile against three base images before each release (`tests/battle/`):

| Image | Libc | Known quirks |
|---|---|---|
| `ubuntu:24.04` | glibc | baseline; most widely deployed |
| `alpine:3.19` | musl | exposes musl-static-link issues in `rusqlite`, `git2`, `aws-sdk-s3` |
| `debian:12` bookworm | glibc | different apt structure from Ubuntu |

Assertions per run: blocks ≥ 82, skills ≥ 43, top hooks ≥ 12, `_lib` hooks ≥ 2; `hooks/_lib/test-gate.sh` runs; `settings.json` validates. "Does it work on a fresh machine?" signal before every version ships.

See `tests/battle/README.md` for running locally.

## Rule references

For the underlying discipline: these mitigations are driven by rules in the user's Claude Code CLAUDE.md. The relevant ones:

- **RULE 0.4** — NO HALLUCINATION / CITATION VERIFY
- **RULE 0.8** — SECRETS SINGLE SOURCE
- **RULE 0.10** — RECURRENCE ESCALATE (same mistake ≥2× → codify via `/escalate-recurrence`)
- **RULE 0.13** — ORCHESTRATOR BRANCH FIRST (agents write files; orchestrator owns git)
- **RULE 0.14** — SESSION SELF-AUDIT
- **RULE 0.15** — SLEEP LAYER (three-phase nightly consolidation)

## Secret hygiene

This repository is public. The `.gitignore` actively blocks commits of:

- `.env` / `.env.*` (except `.env.example`, `.env.template`)
- `secrets/`, `**/secrets/`
- Key files: `*.pem`, `*.key`, `id_rsa*`, `id_ed25519*`

If you accidentally stage a secret:
1. **Do not push.** Drop it from the working tree immediately.
2. **Revoke** the leaked credential at its provider dashboard.
3. **Rotate** any adjacent credentials that may share the leak context.
4. If already pushed to remote: rewrite history (`git filter-repo`) and force-push is NOT safe on a widely-cloned repo; prefer revoke + rotate + new-commit-atop.

The canonical secret store for Claude Code is `~/.claude/secrets/.env`
(chmod 600, RULE 0.8 in your personal umbrella). Project-specific
tokens live at `<repo>/secrets/<name>.env` — both are `.gitignore`'d.
