Security
March 30, 2026 · View on GitHub
This document describes Citadel's security model and defensive measures against common attack vectors.
Threat Model
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
Defenses
1. Path Traversal Protection (protect-files.js)
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
2. Shell Injection Prevention
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
3. Secret Protection
.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.ts
Rationale: Prevents accidental credential leakage in agent conversations or logs.
Test coverage: scripts/test-security.js validates .env reads are blocked
4. Pip Gate (Untracked Dependencies)
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 yet
Rationale: Prevents dependency drift and ensures reproducible builds.
Implementation: quality-gate.js checks git ls-files requirements.txt
Test Suite
Run Security Tests
# Security tests only
node scripts/test-security.js
# Full suite (includes security)
node scripts/test-all.js
What Gets Tested
-
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
Exit Codes
0— All tests pass1— One or more tests failed (DO NOT SHIP)
Fail-Closed Design
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.
Reporting Vulnerabilities
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.
Audit Log
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.
Security Checklist for New Hooks
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