Security
Incident Response Plan
Incident response procedures for the dotfiles repository. Covers supply chain compromise, secrets exposure, configuration drift, tool tampering, and CI pipeline attacks. Based on NIST SP 800-61 Rev. 2 (Computer Security Incident Handling Guide).
Purpose and Scope
This plan defines the detection, triage, containment, eradication, recovery, and post-mortem procedures for security incidents affecting the dotfiles distribution.
In Scope
| Domain | Description |
|---|---|
| Supply chain | Dependency tampering in Homebrew, Nix, Zinit, Neovim plugins, npm, pip |
| Secrets exposure | API keys, SSH keys, tokens leaked to git history, shell history, or logs |
| Configuration drift | Unauthorized changes to deployed dotfiles, chezmoi apply failures |
| Tool compromise | Malicious updates to mise, Nix, or Homebrew-managed binaries |
| CI pipeline | GitHub Actions workflow tampering, compromised action dependencies |
Out of Scope
| Domain | Reason |
|---|---|
| Operating system vulnerabilities | Managed by OS vendor security updates |
| Hardware compromise | Physical security is outside repository scope |
| Third-party SaaS breaches | Upstream provider responsibility (1Password, GitHub) |
Severity Classification Matrix
Severity levels and response times align with the Vulnerability Response SLA defined in COMPLIANCE.md.
| Severity | Definition | Examples | Initial Response | Resolution Target |
|---|---|---|---|---|
| Critical | Active exploitation or secrets exposed in public repository | Leaked API key in git history; compromised CI workflow pushing malicious code; active credential abuse | 24 hours | 48 hours |
| High | Confirmed compromise with no evidence of active exploitation | Unsigned commits merged to main; supply chain dependency with known CVE; tampered binary in PATH | 72 hours | 7 days |
| Medium | Policy violation or misconfiguration with limited blast radius | Chezmoi apply drift on non-sensitive config; TLS bypass pattern in script; stale Nix flake lock | 5 business days | 30 days |
| Low | Informational finding, hardening opportunity, or cosmetic policy gap | Missing CODEOWNERS entry; advisory shellcheck warning; outdated pinned version | 10 business days | 90 days |
Incident Response Phases
Phase 1: Detection
Identify the incident through automated or manual signals.
Automated detection sources:
| Source | Signal | Tool |
|---|---|---|
| Pre-commit hooks | Blocked secret, insecure pattern | Gitleaks, detect-secrets, compliance-guard |
| CI pipeline | Failed security scan, unsigned commit | security-enhanced.yml, compliance-guard.yml |
| Nightly checks | Dependency version drift, CVE match | nightly.yml |
| CodeQL | Static analysis finding | codeql.yml |
| Audit log | Unexpected operation | dot audit (~/.local/share/dotfiles.log) |
Manual detection signals:
- Unexpected
chezmoi diffoutput on a clean system - Unrecognized entries in
git log --show-signature - Binary hash mismatch for tools in
~/.local/bin - Shell startup latency spike (possible injected sourcing)
Phase 2: Triage
Classify the incident severity and assign ownership.
# Gather initial evidence
|
# Check for secrets in recent history
# Validate binary integrity
Triage decision tree:
- Are credentials or secrets exposed publicly? -> Critical
- Is a signed commit chain broken? -> High
- Is the CI pipeline producing unexpected artifacts? -> High
- Is configuration drift limited to non-sensitive files? -> Medium
- Is the finding advisory with no active risk? -> Low
Phase 3: Containment
Stop the incident from spreading. Actions depend on severity.
Immediate containment (Critical/High):
# Revoke exposed credentials
# Revoke SSH certificates
# Lock configuration files to prevent further modification
# Disable compromised CI workflow
# Force-protect the main branch
Short-term containment (Medium/Low):
# Pin the affected dependency to a known-good version
# In .chezmoidata.toml or flake.nix, revert to last verified version
# Re-apply known-good configuration
# Clear potentially tainted caches
Phase 4: Eradication
Remove the root cause of the incident.
| Incident Type | Eradication Action |
|---|---|
| Secrets in git history | Rewrite history with git filter-repo; rotate all exposed credentials |
| Compromised dependency | Pin to patched version; update flake.lock or lazy-lock.json |
| Unsigned commits | Rebase and re-sign the commit chain; enforce branch protection |
| Tampered binary | Reinstall from verified source; validate checksums |
| CI workflow compromise | Audit workflow diff; re-pin actions to verified SHA; rotate GITHUB_TOKEN |
Phase 5: Recovery
Restore normal operations and verify integrity.
# Re-apply dotfiles from clean source
# Verify deployed state matches source
# Run full test suite
# Run compliance checks
# Validate system health
# Confirm audit log captures recovery
|
Phase 6: Post-Mortem
Conduct a structured review within 5 business days of resolution. Use the Post-Incident Review Template below.
Runbooks
Runbook 1: Secrets Leaked to Git History
Detection: Gitleaks pre-commit hook, TruffleHog CI scan, or manual discovery.
# Step 1: Identify exposed secrets
# Step 2: Determine exposure window
# Step 3: Rotate all exposed credentials immediately
# API keys: regenerate in provider dashboard
# SSH keys: generate new keypair and update authorized_keys
# Tokens: revoke and reissue via provider
# Step 4: Remove secrets from git history
# Step 5: Force-push cleaned history (requires branch protection override)
# Step 6: Notify GitHub to purge cached views
# Open a support ticket at https://support.github.com for cache invalidation
# Step 7: Update Gitleaks baseline
# Step 8: Verify clean state
Post-action: Add the leaked pattern to config/gitleaks.toml allowlist if it was a false positive, or add a new rule if the pattern was not previously covered.
Runbook 2: Supply Chain Compromise
Detection: CVE advisory, unexpected binary behavior, hash mismatch, or nightly.yml alert.
# Step 1: Identify the compromised package
# Step 2: Check upstream advisories
# Step 3: Pin to last known-good version
# For Nix: revert flake.lock to known-good commit
# For Homebrew: pin the formula
# For mise: set explicit version
# Step 4: Verify integrity of installed binary
# Compare against published checksums from upstream release
# Step 5: Re-apply configuration with clean dependencies
# Step 6: Run full test suite to detect behavioral changes
Runbook 3: Configuration Drift
Detection: chezmoi diff shows unexpected changes, chezmoi verify fails, or dot health reports drift.
# Step 1: Identify drift scope
|
# Step 2: Capture current deployed state for forensic comparison
# Step 3: Check audit log for unauthorized operations
| |
# Step 4: Determine root cause
# Option A: Local manual edit (benign)
# Option B: External tool modified config (investigate)
# Option C: Compromised apply (escalate to Critical)
# Step 5: Restore to source-of-truth state
# Step 6: Verify restoration
# Step 7: Lock critical configs to prevent recurrence
Runbook 4: Tool Compromise (mise/Nix Managed)
Detection: Unexpected binary behavior, hash mismatch, or CVE disclosure for a managed tool.
# Step 1: Quarantine the affected tool
# Step 2: Record forensic evidence
# Step 3: Check if the tool executed during shell startup
# Step 4: Reinstall from verified source
# Or for Nix:
# Step 5: Verify replacement binary
# Step 6: Clear cached eval output that may reference the compromised tool
# Step 7: Restart shell and verify clean startup
Runbook 5: CI Pipeline Compromise
Detection: Unexpected workflow behavior, modified workflow files, compromised action dependency.
# Step 1: Disable the affected workflow
# Step 2: Audit recent workflow changes
# Step 3: Check action pinning integrity
|
# Step 4: Verify no unauthorized secrets access
|
# Step 5: Review workflow permissions
# Step 6: Restore workflows from known-good state
# Step 7: Re-pin all actions to verified SHA
# Replace tag references with full commit SHA
# Example: actions/checkout@v4 -> actions/checkout@<full-sha>
# Step 8: Re-enable workflow and verify
Communication Protocol
Notification Matrix
| Severity | Notify | Channel | Timeframe |
|---|---|---|---|
| Critical | Repository owner, all contributors | GitHub Security Advisory, direct message | Immediate |
| High | Repository owner | GitHub Issue (private), direct message | Within 24 hours |
| Medium | Repository owner | GitHub Issue | Within 5 business days |
| Low | Tracked in backlog | GitHub Issue with security label | Next review cycle |
Escalation Path
1. Incident detected
└── Automated: CI/hook blocks and logs
└── Manual: Reporter opens private advisory
2. Triage (within Initial Response SLA)
└── Classify severity
└── Assign owner
3. Escalation triggers
└── No response within SLA -> escalate to next severity level
└── Scope expansion -> reclassify severity upward
└── Active exploitation confirmed -> immediate Critical classification
External Notification
| Condition | Action |
|---|---|
| Leaked credentials for third-party service | Notify the service provider to revoke/rotate |
| Compromised upstream dependency | Open issue on upstream repository |
| GitHub Actions vulnerability | Report via GitHub security advisory |
Evidence Preservation
Preserve all forensic evidence before performing eradication or recovery actions.
Evidence Collection Checklist
| Evidence | Command | Storage |
|---|---|---|
| Dotfiles audit log | cp ~/.local/share/dotfiles.log /tmp/incident-$(date +%s)/ | Local archive |
| Git reflog | git reflog > /tmp/incident-$(date +%s)/reflog.txt | Local archive |
| Git signatures | git log --show-signature -50 > /tmp/incident-$(date +%s)/signatures.txt | Local archive |
| Chezmoi state | chezmoi dump --format=json > /tmp/incident-$(date +%s)/chezmoi-state.json | Local archive |
| Binary hashes | sha256sum ~/.local/bin/* > /tmp/incident-$(date +%s)/binary-hashes.txt | Local archive |
| CI run logs | gh run view <run-id> --log > /tmp/incident-$(date +%s)/ci-log.txt | Local archive |
| Shell cache | cp -r ~/.cache/shell/ /tmp/incident-$(date +%s)/shell-cache/ | Local archive |
Structured Log Format
Incident evidence is recorded in JSONL format for automated processing:
# Append structured incident event to log
# Usage
Retention Policy
| Evidence Type | Retention Period |
|---|---|
| Incident logs (JSONL) | 1 year |
| Git reflog snapshots | 90 days |
| Binary hash records | Until next verified release |
| CI run logs | 90 days (GitHub default) |
Recovery Procedures
Standard Recovery
# Roll back to a known-good dotfiles state
# Restore specific configuration files
# Full re-apply from source of truth
# Verify system health
Full System Recovery
For incidents requiring complete re-provisioning:
# Step 1: Export current secrets (if not compromised)
# Step 2: Clear all deployed dotfiles
# Step 3: Clear all caches
# Step 4: Re-initialize from clean clone
# Step 5: Re-apply
# Step 6: Re-import secrets (if exported)
# Step 7: Verify
Recovery Verification Checklist
| Check | Command | Expected |
|---|---|---|
| Chezmoi state clean | chezmoi diff | No output |
| All managed files present | chezmoi verify | Exit code 0 |
| Test suite passes | ./tests/framework/test_runner.sh | All assertions pass |
| Health dashboard green | dot health | No errors |
| Signatures valid | git log --show-signature -5 | All commits signed |
| Pre-commit hooks active | pre-commit run --all-files | All hooks pass |
Post-Incident Review Template
Conduct a review within 5 business days of resolution. Copy the template below into a new file under docs/security/incidents/.
-
-