security-audit
You are performing a security audit of Andrew's Claude environment. Your job is to inventory all access, validate tokens where possible, flag anything concerning, and produce a clear report with remediation steps.
Step 1 — Collect local config
Run the following in a single Bash tool call:
echo "=DATE=$(date '+%Y-%m-%dT%H:%M:%S%z')"
echo "=CLAUDE_JSON="
cat ~/.claude.json 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
servers = data.get('mcpServers', {})
for name, cfg in servers.items():
print(f' [{name}]')
print(f' command: {cfg.get(\"command\", \"\")} {\" \".join(cfg.get(\"args\", [])[:3])}')
envkeys = list(cfg.get('env', {}).keys())
if envkeys:
print(f' env keys: {envkeys}')
" 2>/dev/null || echo "(could not parse ~/.claude.json)"
echo "=ENV_KEYS="
grep -E '^[A-Z_]+=.' ~/.claude/env.sh 2>/dev/null | sed 's/=.*/=***REDACTED***/' || echo "(env.sh not found)"
echo "=SKILLS="
ls ~/dev/claude/skills/ 2>/dev/null
echo "=SETTINGS_PERMISSIONS="
python3 -c "
import json
with open('$HOME/.claude/settings.json') as f:
d = json.load(f)
perms = d.get('permissions', {})
print(' allow:', perms.get('allow', []))
print(' deny:', perms.get('deny', []))
" 2>/dev/null || echo "(could not parse settings.json)"
Then read ~/.claude/env.sh to get the list of token variable names (not values) that are set.
Step 2 — Validate tokens
For each token found, test validity. Use Bash tool for each check — never print token values.
GitHub PAT (GITHUB_TOKEN)
result=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user)
echo "GITHUB_TOKEN status: $result"
# Get scopes
curl -sI -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user 2>/dev/null | grep -i x-oauth-scopes || echo "(fine-grained PAT — scopes not exposed via header)"
# Get accessible repos
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/installation/repositories" -o /dev/null -w "%{http_code}" 2>/dev/null
HuggingFace token (HF_TOKEN)
result=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $HF_TOKEN" https://huggingface.co/api/whoami)
echo "HF_TOKEN status: $result"
LinkedIn token (LINKEDIN_TOKEN)
result=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $LINKEDIN_TOKEN" "https://api.linkedin.com/v2/userinfo")
echo "LINKEDIN_TOKEN status: $result"
Webex token (WEBEX_TOKEN)
result=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $WEBEX_TOKEN" "https://webexapis.com/v1/people/me")
echo "WEBEX_TOKEN status: $result"
Splunk MCP token (SPLUNK_TOKEN)
result=$(curl -sk -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $SPLUNK_TOKEN" "https://${SPLUNK_HOST}:8089/services/mcp" -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"audit","version":"1.0"}}}')
echo "SPLUNK_TOKEN status: $result"
Splunk API token (SPLUNK_API_TOKEN)
result=$(curl -sk -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $SPLUNK_API_TOKEN" "https://${SPLUNK_HOST}:8089/services/server/info")
echo "SPLUNK_API_TOKEN status: $result"
Skip any check where the variable is empty or unset. Record HTTP status codes:
- 200/201 — valid
- 401/403 — expired or invalid
- 0/000 — unreachable (network or host issue)
Step 3 — Audit filesystem access
The filesystem MCP server grants Claude read/write access to ~/dev/. Audit what is actually there.
Run the following in a single Bash tool call:
echo "=FILESYSTEM_ROOT="
ls ~/dev/
echo "=REPO_LIST="
for d in ~/dev/*/; do
if [ -d "$d/.git" ]; then
remote=$(git -C "$d" remote get-url origin 2>/dev/null || echo "(no remote)")
echo " GIT: $d → $remote"
else
echo " DIR: $d"
fi
done
echo "=SENSITIVE_FILES="
# Look for files that shouldn't be accessible: secrets, keys, certs, env files
find ~/dev -maxdepth 3 \( \
-name "*.pem" -o -name "*.key" -o -name "*.p12" -o -name "*.pfx" \
-o -name ".env" -o -name ".env.*" -o -name "*.env" \
-o -name "secrets.json" -o -name "credentials.json" \
-o -name "*.secret" -o -name "id_rsa" -o -name "id_ed25519" \
\) 2>/dev/null | head -50
echo "=GITIGNORED_BUT_PRESENT="
# Check each git repo for files that are gitignored but exist (potential committed secrets)
for d in ~/dev/*/; do
if [ -d "$d/.git" ]; then
ignored=$(git -C "$d" ls-files --others --ignored --exclude-standard 2>/dev/null | grep -E "\.(env|pem|key|secret|token)$" | head -5)
if [ -n "$ignored" ]; then
echo " $d: $ignored"
fi
fi
done
echo "=ENV_FILES_IN_DEV="
find ~/dev -maxdepth 3 -name "*.env" -o -name ".env" -o -name "env.sh" 2>/dev/null | grep -v ".git"
echo "=WORLD_READABLE_SECRETS="
# Check ~/.claude/env.sh permissions — should not be world-readable
ls -la ~/.claude/env.sh 2>/dev/null
ls -la ~/.claude.json 2>/dev/null
Assess the findings:
- List all repos under
~/dev/— note any that seem unexpected or sensitive - Flag any sensitive files (keys, certs, .env files) — these are readable by Claude via the filesystem MCP
- Check file permissions on
env.shand.claude.json— should be600(owner read/write only) - Note the blast radius — if the filesystem MCP were misused, what could be read or modified?
Step 4 — Audit Claude Code permissions
Claude Code's tool permissions are controlled by two files. Audit both.
Run the following in a single Bash tool call:
echo "=SETTINGS_JSON_PERMISSIONS="
python3 -c "
import json, os
path = os.path.expanduser('~/.claude/settings.json')
with open(path) as f:
d = json.load(f)
perms = d.get('permissions', {})
allow = perms.get('allow', [])
deny = perms.get('deny', [])
print('allow rules:', len(allow))
for r in allow:
print(' ALLOW:', r)
print('deny rules:', len(deny))
for r in deny:
print(' DENY:', r)
" 2>/dev/null || echo "(could not parse ~/.claude/settings.json)"
echo "=PROJECT_SETTINGS="
# Check for project-scoped settings (can override global)
for f in \
~/dev/claude/.claude/settings.json \
~/dev/claude/.claude/settings.local.json; do
if [ -f "$f" ]; then
echo "--- $f ---"
python3 -c "
import json
with open('$f') as f:
d = json.load(f)
perms = d.get('permissions', {})
allow = perms.get('allow', [])
deny = perms.get('deny', [])
print(' allow:', allow)
print(' deny:', deny)
" 2>/dev/null
fi
done
echo "=FILESYSTEM_MCP_ROOT="
python3 -c "
import json, os
with open(os.path.expanduser('~/.claude.json')) as f:
d = json.load(f)
servers = d.get('mcpServers', {})
fs = servers.get('filesystem', {})
args = fs.get('args', [])
# The root path is the last non-flag argument
roots = [a for a in args if not a.startswith('-') and '/' in a]
print('filesystem MCP roots:', roots)
" 2>/dev/null || echo "(could not parse ~/.claude.json)"
echo "=CLAUDEMD_FILES="
# CLAUDE.md files provide project instructions — list all found in accessible paths
find ~/dev -maxdepth 3 -name "CLAUDE.md" 2>/dev/null
Assess the findings:
- Global allow rules (
~/.claude/settings.json) — list each rule; flag any that are overly broad (e.g.Bash(*:*)allows all shell commands without prompting) - Global deny rules — list each; note if no deny rules exist (means nothing is explicitly blocked)
- Project-scoped settings (
.claude/settings.json,.claude/settings.local.json) — these override global settings for this project; flag any that expand permissions beyond the global config - Filesystem MCP root — confirm the registered path matches what is documented; flag if it's broader than necessary (e.g.
~/instead of~/dev/) - CLAUDE.md files — list all found across accessible repos; these provide instructions loaded into Claude's context and could influence behaviour if a repo contains a malicious CLAUDE.md
Flag anything where:
- Allow rules grant unrestricted Bash execution (
Bash(*:*)or similar) - No deny rules exist and allow is also empty (fully permissive — prompts each time, no guardrails)
- Filesystem MCP root is broader than
~/dev/ - Multiple CLAUDE.md files exist across repos (potential prompt injection surface)
Step 5 — Audit MCP server access
For each registered MCP server, assess:
| Server | Transport | Access granted | Token required | Risk notes |
|---|
Flag anything where:
- A server has broad filesystem or network access beyond what skills need
- A token grants more permissions than necessary
- A server is registered but never used by any skill
Step 6 — Audit claude.ai-managed integrations
List the known cloud OAuth integrations (these cannot be validated via token — note they require manual review at claude.ai/settings):
- Gmail — read/send email
- Google Calendar — read/write calendar events
- HuggingFace — model inference, Space invocation
- Slack — read/send messages across channels
For each, note: what access it has, whether it's used by any skill, and how to revoke it if needed (claude.ai/settings → Integrations).
Step 7 — Audit skills
For each skill in ~/dev/claude/skills/, read its SKILL.md and note:
- What external services it calls
- What tokens it requires
- Any shell commands it runs (Bash tool usage)
- Any potential for unintended data exfiltration (e.g. sends content to external APIs)
Step 8 — Audit GitHub PAT permissions
Using the GitHub API response from Step 2, check:
- Is the PAT fine-grained (scoped to specific repos) or classic (broad)?
- Does it have more permissions than documented in CLAUDE.md?
- Is it set to expire, or does it have no expiry?
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user | python3 -c "
import sys, json
d = json.load(sys.stdin)
print('login:', d.get('login'))
print('type:', d.get('type'))
"
Step 9 — Produce the report
Write the report to ~/dev/claude/security-audit-<YYYYMMDD>.md. Use the date from Step 1.
Report format:
# Claude Security Audit
**Date:** <date>
**Audited by:** security-audit skill
---
## Summary
| Area | Status | Issues |
|------|--------|--------|
| MCP Servers | ✅/⚠️/❌ | <count> |
| Filesystem Access | ✅/⚠️/❌ | <count> |
| Claude Code Permissions | ✅/⚠️/❌ | <count> |
| API Tokens | ✅/⚠️/❌ | <count> |
| GitHub PAT | ✅/⚠️/❌ | <count> |
| Claude.ai Integrations | ℹ️ Manual review | — |
| Skills | ✅/⚠️/❌ | <count> |
---
## MCP Servers
<for each server: name, transport, access scope, token used, status, any flags>
---
## Filesystem Access
**Root:** `~/dev/`
### Repos and directories
<list all items under ~/dev/ — git repos with remote URLs, non-git dirs>
### Sensitive files found
<any .env, .pem, .key, credentials files found within ~/dev/ — or "None found">
### File permissions
<permissions on ~/.claude/env.sh and ~/.claude.json — flag if not 600>
### Blast radius assessment
<what could be read or modified if filesystem MCP were misused>
---
## Claude Code Permissions
### Global settings (`~/.claude/settings.json`)
**Allow rules:** <count — or "None (prompts for every tool use)">
<list each rule>
**Deny rules:** <count — or "None (nothing explicitly blocked)">
<list each rule>
### Project settings (`.claude/settings.json`, `.claude/settings.local.json`)
<list any overrides found, or "None">
### Filesystem MCP root
<registered path — flag if broader than ~/dev/>
### CLAUDE.md files in scope
<list all CLAUDE.md files found across ~/dev/ — note any in non-claude repos>
### Assessment
<are permissions appropriately scoped? any overly broad allow rules? any missing deny rules that should exist?>
---
## API Tokens
<for each token: name (not value), service, validity status, expiry if known, risk level>
---
## GitHub PAT
<PAT type, permissions, expiry, risk notes>
---
## Claude.ai-managed Integrations
<list with access description and revocation path>
---
## Skills
<for each skill: external services called, tokens required, risk notes>
---
## Findings
### ❌ Critical
<issues requiring immediate action>
### ⚠️ Warnings
<issues to address soon>
### ℹ️ Informational
<low-risk observations>
---
## Remediation checklist
- [ ] <specific action with instructions>
---
*Report generated by /security-audit skill. Review at: ~/dev/claude/security-audit-<date>.md*
After writing the file, tell the user:
Security audit complete. Report written to
~/dev/claude/security-audit-<date>.md.Summary: X critical, X warnings, X informational.
List the critical and warning findings inline so the user can act immediately without opening the file.
Important: Never print token values at any point. Only reference tokens by variable name.
More from andrewkriley/claude
grill-me
Interview the user relentlessly about a plan, idea, or project until a shared understanding is reached. Walks down each branch of the design tree, resolving dependencies between decisions one-by-one. Use when starting something new or when a plan needs rigorous thinking-through.
10skills
Lists all available Claude Code skills with descriptions and usage hints. Use when you want to know what skills are available or have forgotten a skill name.
3keep-current
Audits README.md, CLAUDE.md, and PROFILE.md against the actual state of the repo — skills, goals, and project direction — and proposes targeted updates. Also infers PROFILE.md refinements from the user's recent communication patterns and questions. Run periodically to keep docs in sync with the project.
3repo-status
Checks the sync status of a git repository — local vs remote branches, commits ahead/behind, open PRs, and working tree state. Works across any project. Use when the user asks "what's the status of the repo", "are local and remote in sync", "check the branches", or "what's the state of dev and main".
2summarise-session
Summarises the current working session — what was worked on, what was achieved, what remains, and any blockers. Use at the end of a session or when handing off work.
2