---
name: eck:audit-security
version: "0.7.4"
description: Scan Claude Code project configuration for security misconfigurations with A-F grading
disable-model-invocation: false
---

# Security Audit Workflow

Scan a Claude Code project's infrastructure files for security misconfigurations, grade each category A-F, produce a weighted composite score, and optionally auto-fix low-risk issues.

---

## Visual Framework

Follow these conventions for all terminal output displayed to the user via the Bash tool.

### Output Mode Awareness

Before producing output, read the user's preferred output mode:

```bash
cat $ECK_HOME/user-preferences.json 2>/dev/null
```

Extract the `outputMode` field and adapt verbosity:

- **guided** — Add explanatory prose after key steps. Describe what is happening and why.
- **standard** (default) — Balanced. Show steps with brief context for non-obvious decisions.
- **concise** — Steps and summary box only. No prose, no explanations.

If the file is missing or `outputMode` is absent, use **standard**.

### ANSI Colors

Declare these variables at the top of **every** Bash tool call (shell state does not persist between calls):

```bash
source $ECK_HOME/colors.sh
```

Every color sequence **must** end with `${RESET}`. No exceptions.

### Stage Banners

Mark major stage transitions with a clean bold header. One blank line before and after.

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}Stage Name${RESET}"
echo ""
```

### Status Symbols

| Symbol | Meaning          | Color       |
| ------ | ---------------- | ----------- |
| `[*]`  | Active / running | `${CYAN}`   |
| `[x]`  | Completed        | `${GREEN}`  |
| `[!]`  | Error            | `${RED}`    |
| `[-]`  | Skipped          | `${DIM}`    |
| `[>]`  | In progress      | `${YELLOW}` |
| `[ ]`  | Pending          | `${DIM}`    |

```bash
printf '%b\n' "${GREEN}[x]${RESET} Step completed"
printf '%b\n' "${RED}[!]${RESET} Something failed"
```

No emoji in output. Use only these bracket symbols for status.

### Summary Blocks

Display at checkpoints and at the end of the skill. One blank line before and after.

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}Title${RESET}"
echo ""
printf '%b\n' "Label: Value"
echo ""
```

### Error Format

```bash
printf '%b\n' "${RED}[!]${RESET} Error message here"
printf '%b\n' "    ${DIM}Recovery guidance here.${RESET}"
```

### Spacing Rules

- One blank line before and after stage banners and summary boxes.
- No blank lines within step lists.
- Maximum output line width: 70 characters.

### Statusline Stage Updates

At the **start** of each stage, update the statusline:

```bash
bash $ECK_HOME/update-stage.sh "{Statusline text from table}"
```

At **skill completion** (success or error), reset:

```bash
bash $ECK_HOME/update-stage.sh
```

---

## Task Registration

| Stage | Subject             | Active Form                  | Statusline      |
| ----- | ------------------- | ---------------------------- | --------------- |
| 1     | Stage 1: Pre-Flight | Verifying prerequisites      | Preflight (1/6) |
| 2     | Stage 2: Scan       | Scanning for vulnerabilities | Scan (2/6)      |
| 3     | Stage 3: Grade      | Grading security posture     | Grade (3/6)     |
| 4     | Stage 4: Report     | Generating report            | Report (4/6)    |
| 5     | Stage 5: Fix        | Applying fixes               | Fix (5/6)       |
| 6     | Stage 6: Persist    | Persisting results           | Persist (6/6)   |

## Usage

```bash
/eck:audit-security                        # Audit current directory
/eck:audit-security <project-path>        # Audit specific project
/eck:audit-security --fix                 # Audit + auto-fix low-risk issues
/eck:audit-security --json                # Output as JSON
/eck:audit-security --category A,B        # Scan specific categories only
```

---

## Stage 1: Pre-Flight

### Inputs

- Target project path from `$ARGUMENTS` (defaults to current directory)
- `<TARGET_PATH>/.claude/` directory and configuration files

### Activities

### Verify Requirements

```bash
# Check target has .claude/
ls {TARGET_PATH}/.claude/

# Read project identity
cat {TARGET_PATH}/.claude/project-constants.md
```

**If no `.claude/` directory:**

```bash
printf '%b\n' "${RED}[!]${RESET} No .claude/ directory found at {TARGET_PATH}"
printf '%b\n' "    ${DIM}This project has no Claude Code infrastructure.${RESET}"
printf '%b\n' "    ${DIM}Run /eck:new-project to bootstrap it.${RESET}"
```

### Collect Target Files

Gather all files that will be scanned:

- `<TARGET_PATH>/CLAUDE.md`
- `<TARGET_PATH>/.mcp.json`
- `<TARGET_PATH>/.claude/settings.json`
- `<TARGET_PATH>/.claude/settings.local.json` (if exists)
- `<TARGET_PATH>/.claude/project-constants.md`
- `<TARGET_PATH>/.claude/hooks/*.sh`
- `<TARGET_PATH>/.claude/agents/*.md`
- `<TARGET_PATH>/.claude/skills/*/skill.md`

### Outputs

- Confirmed target path and list of files to be scanned

### Exit Criteria

- `.claude/` directory found; file inventory collected; or error displayed and skill stops

---

## Stage 2: Scan

### Inputs

- File inventory from Stage 1
- Optional `--category` flag to limit which categories to scan

### Activities

### Category A: Secrets Exposure

Scan CLAUDE.md, .mcp.json, project-constants.md, hook scripts, and agent definitions for hardcoded secrets.

**Patterns to detect:**

- API keys: `sk-[a-zA-Z0-9]{20,}`, `ghp_[a-zA-Z0-9]{36}`, `AKIA[A-Z0-9]{16}`, `xox[bpas]-`
- Passwords: `password\s*[:=]\s*["'][^"']+["']` (not placeholder patterns like `<PASSWORD>`)
- Connection strings with credentials: `://[^:]+:[^@]+@`
- Base64-encoded tokens longer than 40 characters in non-code contexts
- `.env` file contents inlined in configuration

**Severity levels:**

- CRITICAL: Actual API keys or tokens found
- HIGH: Password-like strings in config files
- MEDIUM: Connection strings with embedded credentials
- LOW: Base64 strings that may be tokens

### Category B: Hook Injection Risk

Scan all `.sh` files in `.claude/hooks/` for injection vulnerabilities.

**Patterns to detect:**

- Unquoted `$INPUT` usage (should always be quoted: `"$INPUT"`)
- `eval` statements with variable expansion
- Piping to `sh`, `bash`, or `exec` from variable content
- Missing `set -e` or `set -o pipefail` in scripts that modify files
- World-writable permissions in hook scripts

**Severity levels:**

- CRITICAL: `eval` with unvalidated input
- HIGH: Unquoted variable in command position
- MEDIUM: Pipe to shell interpreter
- LOW: Missing error handling flags

### Category C: MCP Server Risk

Scan `.mcp.json` for MCP server configuration issues.

**Patterns to detect:**

- Unscoped tool grants (no `allowedTools` restriction)
- External URLs without version pinning (e.g., `npx` without `@version`)
- `stdio` transport with absolute paths outside project
- Environment variables passed that may contain secrets
- Unknown or untrusted MCP server packages

**Severity levels:**

- HIGH: Unscoped tool grants to external servers
- MEDIUM: Unpinned external package versions
- LOW: Environment pass-through without filtering

### Category D: Agent Config

Scan agent definitions in `.claude/agents/` for risky patterns.

**Patterns to detect:**

- Instruction override patterns: `ignore previous`, `disregard`, `override` in agent prompts
- Agents with Write/Edit tools that can modify their own definition files
- Agents with Bash tool and no scope restriction
- Missing model specification (inherits default, may be unintentionally powerful)
- Agent prompts that reference files outside project boundary

**Severity levels:**

- HIGH: Self-modifying agent definitions
- MEDIUM: Unrestricted Bash access
- LOW: Missing model specification

### Category E: Settings Permissions

Scan `settings.json` and `settings.local.json` for permission issues.

**Patterns to detect:**

- `dangerouslySkipPermissions: true` or similar bypass flags
- Overly broad Bash allowlists (e.g., `"*"` patterns)
- Wildcard matchers that bypass safety hooks
- Missing deny rules for destructive operations
- PreToolUse hooks with empty matcher arrays (catch-all without intent)
- Safety hooks (protect-files, detect-secrets) not wired

**Severity levels:**

- CRITICAL: Permission bypass flags
- HIGH: Wildcard Bash allowlists
- MEDIUM: Missing safety hooks
- LOW: Broad but bounded matchers

### Agent-Assisted Deep Analysis

After the pattern-matching scan completes, dispatch the `security-specialist` agent for deeper analysis of high-severity findings.

**When to dispatch:**

- One or more CRITICAL or HIGH findings detected in any category
- Skip when all findings are MEDIUM or LOW (pattern-matching is sufficient)

1. **MUST** dispatch `security-specialist` agent via `agent:dispatch`. Do NOT skip agent analysis when CRITICAL or HIGH findings exist:
   - **Prompt:** "Review the following security findings from a Claude Code project infrastructure audit. For each CRITICAL or HIGH finding, validate whether it is a true positive or false positive, assess the actual risk in context, and provide specific remediation recommendations beyond template fixes."
   - **Context:**
     - List of CRITICAL/HIGH findings with severity, category, file path, and matched pattern
     - Surrounding file content (10 lines before/after each finding)
     - Project name and configuration context from project-constants.md
   - **Expected output:**
     - Per-finding: `true_positive` / `false_positive` assessment
     - Adjusted severity (if false positive, downgrade to INFO)
     - Specific remediation steps (beyond generic template fixes)
     - Risk context: what an attacker could exploit and under what conditions

2. Merge agent findings with pattern-matching results:
   - False positives identified by the agent are downgraded but still reported (marked as `[FP]`)
   - Agent remediation recommendations replace generic template-based remediations
   - Agent risk context is appended to finding descriptions in the report
   - If agent is unavailable or fails, fall back to pattern-matching-only results
   - Log: `"Agent-assisted analysis: {available/unavailable}, {N} findings reviewed"`

**Graceful fallback:** If agent dispatch fails or times out, continue with pattern-matching-only results. Log a warning but do not STOP the audit. This mirrors the pattern in `validate-security` where `security-specialist` dispatch failure is logged but doesn't block.

### Outputs

- Per-category finding lists with severity levels (CRITICAL/HIGH/MEDIUM/LOW)

### Exit Criteria

- All selected categories scanned; agent-assisted analysis merged (if applicable)

---

## Stage 3: Grade

### Inputs

- Per-category findings from Stage 2

### Activities

### Per-Category Grading

Calculate a letter grade for each category based on findings:

| Grade | Criteria                                  |
| ----- | ----------------------------------------- |
| A     | No findings                               |
| B     | LOW findings only                         |
| C     | Up to 2 MEDIUM findings, no HIGH/CRITICAL |
| D     | Any HIGH finding or 3+ MEDIUM findings    |
| F     | Any CRITICAL finding                      |

### Weighted Composite Score

| Category                | Weight | Rationale                        |
| ----------------------- | ------ | -------------------------------- |
| A: Secrets Exposure     | 30%    | Highest impact — leaked keys     |
| B: Hook Injection       | 20%    | Code execution risk              |
| C: MCP Server Risk      | 20%    | Supply chain and scope risk      |
| D: Agent Config         | 15%    | Indirect risk via agent behavior |
| E: Settings Permissions | 15%    | Access control foundation        |

**Composite calculation:**

- A=4, B=3, C=2, D=1, F=0
- Weighted score = sum(grade_value \* weight)
- Map back: 3.5-4.0=A, 2.5-3.49=B, 1.5-2.49=C, 0.5-1.49=D, 0-0.49=F

### Outputs

- Per-category letter grades (A-F)
- Weighted composite score and overall grade

### Exit Criteria

- All categories graded; composite score computed

---

## Stage 4: Report

### Inputs

- Grades and findings from Stage 3
- Optional `--json` flag

### Activities

### Report Format

Display the report header as a summary box and category grades with status symbols.

```bash
source $ECK_HOME/colors.sh

echo ""
printf '%b\n' "${ORANGE}${BOLD}Security Audit${RESET}"
echo ""
printf '%b\n' "Project: {PROJECT_NAME}"
printf '%b\n' "Path: {TARGET_PATH}"
printf '%b\n' "Date: {DATE}"
printf '%b\n' "Overall Grade: {COMPOSITE_GRADE}"
echo ""
```

Category grades use severity-mapped status symbols:

- Grade A/B: `${GREEN}[x]${RESET}`
- Grade C: `${CYAN}[*]${RESET}`
- Grade D: `${YELLOW}[>]${RESET}`
- Grade F: `${RED}[!]${RESET}`

```bash
printf '%b\n' "${ORANGE}${BOLD}Category Grades${RESET}"
echo ""
# For each category, use the appropriate symbol:
printf '%b\n' "${GREEN}[x]${RESET} A: Secrets Exposure    Grade: A  Findings: 0   (30%)"
printf '%b\n' "${YELLOW}[>]${RESET} B: Hook Injection      Grade: D  Findings: 3   (20%)"
printf '%b\n' "${GREEN}[x]${RESET} C: MCP Server Risk     Grade: B  Findings: 1   (20%)"
printf '%b\n' "${CYAN}[*]${RESET} D: Agent Config        Grade: C  Findings: 2   (15%)"
printf '%b\n' "${RED}[!]${RESET} E: Settings Perms      Grade: F  Findings: 1   (15%)"
```

Findings use severity-colored symbols:

- CRITICAL: `${RED}[!]${RESET}`
- HIGH: `${YELLOW}[>]${RESET}`
- MEDIUM: `${CYAN}[*]${RESET}`
- LOW: `${DIM}[-]${RESET}`

```bash
echo ""
printf '%b\n' "${ORANGE}${BOLD}Findings${RESET}"
echo ""
printf '%b\n' "  ${RED}[!]${RESET} CRITICAL  {file}  {description}"
printf '%b\n' "              ${DIM}{remediation}${RESET}"
printf '%b\n' "  ${YELLOW}[>]${RESET} HIGH      {file}  {description}"
printf '%b\n' "              ${DIM}{remediation}${RESET}"
printf '%b\n' "  ${CYAN}[*]${RESET} MEDIUM    {file}  {description}"
printf '%b\n' "              ${DIM}{remediation}${RESET}"
printf '%b\n' "  ${DIM}[-]${RESET} LOW       {file}  {description}"
printf '%b\n' "              ${DIM}{remediation}${RESET}"
```

### JSON Output (`--json`)

If `--json` flag is specified, output the report as a JSON object instead of markdown:

```json
{
  "project": "{PROJECT_NAME}",
  "date": "{DATE}",
  "composite_grade": "{GRADE}",
  "categories": {
    "secrets_exposure": {"grade": "A", "findings": []},
    ...
  }
}
```

### Outputs

- Formatted security report displayed in terminal or as JSON

### Exit Criteria

- Report displayed with all findings and remediation guidance

---

## Stage 5: Fix (`--fix`)

### Inputs

- Findings from Stage 2
- `--fix` flag (must be present)

### Activities

**Only execute if `--fix` flag was specified.**

### Auto-Fixable Issues (Low Risk)

For each fixable finding, present via AskUserQuestion before applying:

| Finding                                       | Auto-Fix Action                   |
| --------------------------------------------- | --------------------------------- |
| Missing `.gitignore` entries for secret files | Append entries to `.gitignore`    |
| Safety hooks not wired in settings.json       | Add hook entries to settings.json |
| Unquoted `$INPUT` in hooks                    | Quote the variable                |
| Missing `set -e` in hooks                     | Add to script header              |

### Non-Auto-Fixable Issues

Report these with manual remediation steps:

- Hardcoded secrets (require rotation)
- MCP server scope changes (require understanding of usage)
- Agent permission changes (require review of workflow)

### Outputs

- Auto-fixed files (low-risk items user approved)
- Manual remediation guidance for non-auto-fixable items

### Exit Criteria

- All approved auto-fixes applied; non-fixable items reported

---

## Stage 6: Persist

### Inputs

- Full report from Stage 4
- `$ECK_HOME/registry.yaml` (if exists)

### Activities

Save the audit report for tracking:

```bash
mkdir -p $ECK_HOME/audits/security/
```

**Report location:**

```
$ECK_HOME/audits/security/{project-name}-{YYYY-MM-DD}.md
```

If a report already exists for this project + date, overwrite it.

### Registry Update

After security scan completes, update the project registry.

**Reference:** `ops:registry-update` primitive for the canonical write procedure.

If registry does not exist, log warning and skip this step.

If the project entry is not found in the registry, log warning: 'Project not found in registry. Run /eck:new-project or /eck:audit-unmanaged-project first.' and skip registry update.

Set `last_modified_at` to current ISO-8601 date.

Append a sync_history entry to the project's registry record:

```yaml
- date: "{DATE}"
  action: "security-audit"
  performed_by: "/eck:audit-security"
  summary: "Security grade: {GRADE}, {N} findings ({N} critical, {N} high)"
```

Recompute `project_summary` per the `ops:registry-update` primitive.

Use atomic write: write to `registry.yaml.tmp`, then rename to `registry.yaml`.

### Outputs

- Audit report saved to `$ECK_HOME/audits/security/`
- `registry.yaml` updated with audit results

### Exit Criteria

- Report persisted; registry updated; statusline reset

---

## Error Handling

### No .claude Directory

```bash
printf '%b\n' "${RED}[!]${RESET} No .claude/ directory found at {TARGET_PATH}"
printf '%b\n' "    ${DIM}Run /eck:new-project to bootstrap infrastructure.${RESET}"
```

### Partial Scan

```bash
printf '%b\n' "${YELLOW}[>]${RESET} Could not read {N} files during scan."
printf '%b\n' "    ${DIM}Skipped files:${RESET}"
printf '%b\n' "    ${DIM}  - {path}: {reason}${RESET}"
printf '%b\n' ""
printf '%b\n' "    ${DIM}Audit results may be incomplete.${RESET}"
```

### No Findings

```bash
printf '%b\n' "${GREEN}[x]${RESET} Security Audit: Grade A -- No issues found."
printf '%b\n' "    All 5 categories passed with no findings."
```

---

## Related Documents

- [/eck:audit-unmanaged-project skill](../audit-unmanaged-project/SKILL.md) — Infrastructure audit
- [security-gate.sh hook]($ECK_HOME/.claude/hooks/security-gate.sh) — Pre-commit scanner
- [detect-secrets.sh hook]($ECK_HOME/.claude/hooks/detect-secrets.sh) — Secret filename detection
