This document describes Citadel's security model and defensive measures against common attack vectors.
Citadel hooks run in the same security context as Claude Code itself. The primary threats are:
- Path Traversal — Malicious or confused agents attempting to access files outside the project root
- Shell Injection — Command injection via unsanitized user/agent input in shell commands
- Secret Leakage — Agents inadvertently reading .env files or other credential stores
- Untracked Dependencies — Installing packages not declared in version control
All file operations (Read/Write/Edit) are validated before execution:
// Blocks: ../../../etc/passwd
// Blocks: /etc/passwd (absolute path outside project)
// Allows: src/components/Button.tsx (relative to project root)Implementation:
- Regex-based detection of
../and..\sequences - Absolute path validation against
PROJECT_ROOT - Fail-closed design: unexpected errors block the action
Test coverage: scripts/test-security.js validates traversal attacks are blocked
All git operations use execFileSync with array arguments instead of shell strings:
// ✅ Safe (no shell interpretation):
execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' })
// ❌ Vulnerable (shell injection risk):
execSync(`git rev-parse ${branch}`) // if branch = "; rm -rf /"Files using execFileSync:
quality-gate.js— git operations for lint checksharness-health-util.js— git status queriespost-edit.js— file tracking
Test coverage: scripts/test-security.js verifies safe APIs are used
.env files and variants (.env.local, .env.production, etc.) are blocked from Read operations:
// Blocks: .env, .env.local, .env.production
// Allows: README.md, src/config.tsRationale: Prevents accidental credential leakage in agent conversations or logs.
Test coverage: scripts/test-security.js validates .env reads are blocked
Before allowing pip install, checks if requirements.txt is tracked by git:
// ✅ Allows: pip install when requirements.txt is committed
// ⚠️ Warns: pip install when requirements.txt exists but is untracked
// ✅ Allows: pip install when no requirements.txt exists yetRationale: Prevents dependency drift and ensures reproducible builds.
Implementation: quality-gate.js checks git ls-files requirements.txt
# Security tests only
node scripts/test-security.js
# Full suite (includes security)
node scripts/test-all.js-
Path Traversal
../../../etc/passwd→ blocked/etc/passwd→ blockedsrc/file.ts→ allowed
-
Shell Injection
- Verify
execFileSyncusage (safe) - Reject
execSyncusage (unsafe) - Validate git commands use array args
- Verify
-
Secret Protection
.envreads → blocked- Regular file reads → allowed
-
Glob Pattern Security
secrets/**patterns work correctly- Recursive
**globs match expected files
0— All tests pass1— One or more tests failed (DO NOT SHIP)
All security hooks follow a fail-closed approach:
- Unexpected errors → block the action (exit 2)
- Parse failures → block the action
- Missing validation → block the action
This prevents security bypasses via error conditions.
If you discover a security issue:
- Do not open a public GitHub issue
- Email: security@citadel.dev (or create private security advisory)
- Include: steps to reproduce, impact assessment, suggested fix
We'll respond within 48 hours and coordinate disclosure timing.
All security-relevant events are logged to .planning/telemetry/audit.jsonl:
{
"schema": 1,
"event": "blocked",
"hook": "protect-files",
"reason": "path traversal sequence",
"file": "../../../etc/passwd",
"timestamp": "2026-03-30T12:05:00.000Z"
}Use this log for forensic analysis and security monitoring.
When adding a new hook that handles user/agent input:
- Use
health.validatePath()for all file paths - Use
health.validateCommand()for all shell commands - Use
execFileSyncwith array args (notexecSyncorexec) - Fail closed: unexpected errors exit 2 (block), not 0 (allow)
- Add test cases to
scripts/test-security.js - Document security assumptions in hook header comment