Skip to content

Commit 2fa9db1

Browse files
authored
Merge pull request #4 from lorecraft-io/fix/security-audit-2026-04
Security hardening: credentials.json gitignore, CI least-privilege, SHA-pin ShellCheck
2 parents a4c2c87 + a55e6a8 commit 2fa9db1

12 files changed

Lines changed: 113 additions & 31 deletions

File tree

.github/workflows/lint.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ on:
66
pull_request:
77
branches: [main]
88

9+
permissions:
10+
contents: read
11+
912
jobs:
1013
shellcheck:
1114
runs-on: ubuntu-latest
1215
steps:
1316
- uses: actions/checkout@v4
1417
- name: Run ShellCheck
15-
uses: ludeeus/action-shellcheck@2.0.0
18+
uses: ludeeus/action-shellcheck@94e0aab03ca135d11a35e5bfc14e6746dc56e7e9 # v2.0.0
1619
with:
1720
scandir: '.'
1821
severity: warning

.github/workflows/security.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Security Checks
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
- cron: '0 9 * * 1' # Weekly Monday 9am UTC
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
shellcheck-strict:
16+
name: ShellCheck (error severity)
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Run ShellCheck at error severity
21+
uses: ludeeus/action-shellcheck@94e0aab03ca135d11a35e5bfc14e6746dc56e7e9 # v2.0.0
22+
with:
23+
scandir: '.'
24+
severity: error
25+
ignore_paths: terminal-academy node_modules
26+
27+
secret-scan:
28+
name: Secret scanning
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@v4
32+
with:
33+
fetch-depth: 0
34+
- name: Scan for hardcoded secrets
35+
run: |
36+
# Fail if real-looking API keys are found in tracked files
37+
if git ls-files | xargs grep -lniE \
38+
"(sk-ant-api|ghp_[0-9A-Za-z]{36}|xoxb-[0-9A-Za-z-]+|AKIA[0-9A-Z]{16})" \
39+
2>/dev/null | grep -v ".git"; then
40+
echo "::error::Potential hardcoded secrets detected — see matches above"
41+
exit 1
42+
fi
43+
echo "No hardcoded secrets detected"
44+
45+
download-url-check:
46+
name: Verify pinned download URLs are reachable
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@v4
50+
- name: Check SKILL_URL in step-9
51+
run: |
52+
COMMIT=$(grep 'SKILL_COMMIT=' step-9/step-9-install.sh | head -1 | cut -d'"' -f2)
53+
URL="https://raw.githubusercontent.com/lorecraft-io/cli-maxxing/${COMMIT}/step-9/safetycheck-skill/SKILL.md"
54+
HTTP_STATUS=$(curl -o /dev/null -s -w "%{http_code}" "$URL")
55+
if [ "$HTTP_STATUS" != "200" ]; then
56+
echo "::error::Pinned SKILL_URL returned HTTP $HTTP_STATUS — commit SHA may be invalid"
57+
exit 1
58+
fi
59+
echo "SKILL_URL OK (HTTP 200) for commit $COMMIT"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ CLAUDE.md
1818
*.cert
1919
*.p12
2020
*.pfx
21+
credentials.json
2122

2223
# Node (defensive — step scripts may install deps)
2324
node_modules/

README-SECTIONS/cheat-sheet.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ These are custom skills installed by the setup scripts. Type them inside a Claud
9696
| `/rmini do the thing` | Step 3 | Launch a compact 5-agent swarm — same power, tighter team |
9797
| `/rhive <goal>` | Step 3 | Launch a queen-led autonomous hive-mind with raft consensus |
9898
| `/w4w` | Step 3 | Maximum attention to detail — word for word, line for line. No skipping, no summarizing. Also works without the slash — just type `w4w` |
99+
| `/safetycheck` | Step 9 | Security audit — scans any project for exposed keys, missing rate limiting, input sanitization gaps, dependency vulnerabilities, and insecure configurations. Also responds to "run a safety check" in plain English |
99100

100101

101-
> These are **explicit triggers** — you type the command to activate the skill. This is different from the auto-triggered tools below, which respond to natural language. Exception: `/w4w` also works without the slash — just type `w4w` anywhere in your message. `/rmini` is the compact version of `/rswarm` — 5 agents instead of 15.
102+
> These are **explicit triggers** — you type the command to activate the skill. This is different from the auto-triggered tools below, which respond to natural language. Exception: `/w4w` also works without the slash — just type `w4w` anywhere in your message. `/rmini` is the compact version of `/rswarm` — 5 agents instead of 15. `/safetycheck` also works in natural language.
102103
103104
---
104105

@@ -125,7 +126,7 @@ These activate on their own when Claude detects a relevant task via natural lang
125126
| Excalidraw | Add-on | Natural language — diagrams, flowcharts, whiteboard sketches | "Draw a system architecture diagram" |
126127
| Gamma | Add-on | Natural language — presentations, documents, webpages | "Create a pitch deck for my startup" |
127128

128-
> **Key distinction:** Slash commands (`/rswarm`, `/rmini`, `/rhive`, `/w4w`) require you to type the command. Everything in this table works by just talking to Claude naturally.
129+
> **Key distinction:** Slash commands (`/rswarm`, `/rmini`, `/rhive`, `/w4w`, `/safetycheck`) require you to type the command. Everything in this table works by just talking to Claude naturally.
129130
>
130131
> **Add-on tools** are not part of the step-by-step setup — they're optional MCP servers you can connect separately. Claude auto-detects them when they're installed.
131132

README-SECTIONS/step-ordering.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Run the steps in this order:
1414
| 6 | Productivity Tools | Motion Calendar + Notion (pick what you use) |
1515
| 7 | Second Brain | Obsidian vault setup + data import (7a-7d) |
1616
| 8 | Telegram | Telegram bot setup — message Claude from your phone |
17+
| 9 | Safety Check | Security auditing — 8 API checks + 12 MCP checks for tool poisoning, DNS rebinding, supply chain attacks |
1718
| **Final** | **Status Line** | **Final config — status indicators, system health check** |
1819

19-
> **Note:** Step 6 (Productivity Tools) is all optional — install only the tools you use. Step 7 (Second Brain) is the biggest step with four sub-parts (7a-7d). Step 8 (Telegram) is interactive — it walks you through creating a bot and pasting your token. The Final Step (Status Line) is the wrap-up that wires everything together — your status indicators show what's active across all the tools.
20+
> **Note:** Step 6 (Productivity Tools) is all optional — install only the tools you use. Step 7 (Second Brain) is the biggest step with four sub-parts (7a-7d). Step 8 (Telegram) is interactive — it walks you through creating a bot and pasting your token. Step 9 (Safety Check) installs the `/safetycheck` security audit skill — 8 core checks for any project, plus 12 MCP-specific checks when an MCP project is detected (20 total). The Final Step (Status Line) is the wrap-up that wires everything together — your status indicators show what's active across all the tools.

step-1/step-1-install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ install_claude_code() {
257257
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_RC"
258258
success "Added ~/.local/bin to PATH in $SHELL_RC"
259259
else
260-
success "~/.local/bin already in PATH"
260+
success "$HOME/.local/bin already in PATH"
261261
fi
262262

263263
# Install cbrain command (2ndBrain + skip-permissions)

step-7/step-7d-wire-vault.sh

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,14 @@ echo " Projects: $PROJECT_COUNT project folders"
9090
echo " Total: $TOTAL_NOTES notes"
9191
echo ""
9292

93-
# Auto-link orphan files to their parent project
93+
# Auto-link orphan files to their parent project (idempotent — skips if link already present)
9494
info "Linking orphan files to parent projects..."
9595
LINK_COUNT=0
9696
while IFS= read -r f; do
97-
if ! grep -q '\[\[' "$f" 2>/dev/null; then
98-
project=$(echo "$f" | sed "s|$VAULT_PATH/07-Projects/||" | cut -d'/' -f1)
99-
if [ -n "$project" ]; then
100-
printf "\n\n---\n[[%s]]\n" "$project" >> "$f"
101-
LINK_COUNT=$((LINK_COUNT + 1))
102-
fi
97+
project=$(echo "$f" | sed "s|$VAULT_PATH/07-Projects/||" | cut -d'/' -f1)
98+
if [ -n "$project" ] && ! grep -qF "[[${project}]]" "$f" 2>/dev/null; then
99+
printf "\n\n---\n[[%s]]\n" "$project" >> "$f"
100+
LINK_COUNT=$((LINK_COUNT + 1))
103101
fi
104102
done < <(find "$VAULT_PATH/07-Projects" -name '*.md')
105103
success "Linked orphan files to parent projects"

step-9/safetycheck-skill/SKILL.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ git ls-files 2>/dev/null | grep -iE "\.env$"
6767

6868
**MCP Config scan** (if MCP detected) — Scan `.mcp.json`, `claude_desktop_config.json`, `.cursor/mcp.json` for hardcoded secrets in `env` blocks:
6969
```bash
70-
grep -r '"env"' .mcp.json claude_desktop_config.json .cursor/mcp.json 2>/dev/null | grep -iE '(sk-[a-zA-Z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[a-zA-Z0-9]{36}|AIzaSy[a-zA-Z0-9_-]{30,}|xox[bpsa]-[a-zA-Z0-9-]+)'
70+
grep -rn '"env"' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null | grep -iE '(sk-[a-zA-Z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[a-zA-Z0-9]{36}|AIzaSy[a-zA-Z0-9_-]{30,}|xox[bpsa]-[a-zA-Z0-9-]+)'
7171
```
7272

7373
Check if MCP configs are tracked in git:
@@ -344,7 +344,7 @@ Verify TLS is enforced and DNS rebinding protection is active.
344344
**Checks:**
345345
```bash
346346
# Check for HTTP (non-HTTPS, non-localhost) in MCP configs
347-
grep -rniE '"url"\s*:\s*"http://' .mcp.json claude_desktop_config.json 2>/dev/null | grep -vE '(localhost|127\.0\.0\.1|::1)'
347+
grep -rniE '"url"\s*:\s*"http://' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null | grep -vE '(localhost|127\.0\.0\.1|::1)'
348348

349349
# Check for 0.0.0.0 binding without auth
350350
grep -rniE '(0\.0\.0\.0|host:\s*["'"'"']0\.0\.0\.0)' --include="*.ts" --include="*.js" --include="*.py" .
@@ -395,10 +395,10 @@ Check for over-privileged tokens, missing expiration, and insecure storage.
395395

396396
```bash
397397
# Check for wildcard/broad OAuth scopes in MCP config or auth code
398-
grep -rniE '(mail\.google\.com/|calendar\.google\.com/|drive\.google\.com/|scope.*\*|scope.*"all"|scope.*"full")' --include="*.ts" --include="*.js" --include="*.py" .mcp.json 2>/dev/null
398+
grep -rniE '(mail\.google\.com/|calendar\.google\.com/|drive\.google\.com/|scope.*\*|scope.*"all"|scope.*"full")' --include="*.ts" --include="*.js" --include="*.py" --include=".mcp.json" . 2>/dev/null
399399

400400
# Check for access tokens stored in plaintext
401-
grep -rniE '("access_token"\s*:\s*"[^"]{20,}"|token\s*=\s*["'"'"'][^"'"'"']{20,})' .mcp.json claude_desktop_config.json 2>/dev/null
401+
grep -rniE '("access_token"\s*:\s*"[^"]{20,}"|token\s*=\s*["'"'"'][^"'"'"']{20,})' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null
402402

403403
# Check for long-lived tokens (no expiry)
404404
grep -rniE '(expires_in.*86400|expires_in.*[0-9]{6,}|no.*expir|never.*expir)' --include="*.ts" --include="*.js" .
@@ -486,10 +486,10 @@ grep -rn "hostHeaderValidation\|localhostHostValidation\|createMcpExpressApp" --
486486

487487
```bash
488488
# @latest floating versions in MCP config (rug-pull risk)
489-
grep -rniE '"@latest"|npx.*@latest' .mcp.json claude_desktop_config.json .cursor/mcp.json 2>/dev/null
489+
grep -rniE '"@latest"|npx.*@latest' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null
490490

491491
# npx -y without pinned version (auto-install from potentially poisoned package)
492-
grep -rniE 'npx.*-y' .mcp.json claude_desktop_config.json 2>/dev/null | grep -vE '@[0-9]'
492+
grep -rniE 'npx.*-y' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null | grep -vE '@[0-9]'
493493

494494
# Lockfile check
495495
ls package-lock.json yarn.lock pnpm-lock.yaml bun.lockb 2>/dev/null || echo "NO_LOCKFILE"
@@ -498,7 +498,7 @@ ls package-lock.json yarn.lock pnpm-lock.yaml bun.lockb 2>/dev/null || echo "NO_
498498
node -e "const p=require('./package.json'); console.log(p.files ? 'HAS_FILES_WHITELIST' : 'NO_FILES_WHITELIST');" 2>/dev/null
499499

500500
# Shell metacharacters in MCP config args (command injection via config)
501-
grep -rniE '"args"\s*:\s*\[' .mcp.json claude_desktop_config.json 2>/dev/null | grep -E '[;|&\$\`]'
501+
grep -rniE '"args"\s*:\s*\[' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null | grep -E '[;|&\$\`]'
502502
```
503503

504504
**Severity**: HIGH for `@latest` in MCP config. HIGH for no lockfile. HIGH for shell metacharacters in args arrays. MEDIUM for no files whitelist on published MCP server. PASS if pinned and locked.
@@ -515,13 +515,13 @@ Verify tool invocations are logged with structured data.
515515

516516
```bash
517517
# Check for structured logging library
518-
grep -rn "winston\|pino\|bunyan\|log4js\|structlog\|logging\.getLogger" package.json requirements.txt 2>/dev/null
518+
grep -rn "winston\|pino\|bunyan\|log4js\|structlog\|logging\.getLogger" . --include="package.json" --include="requirements.txt" 2>/dev/null
519519

520520
# Check for MCP logging notifications
521521
grep -rn "sendLoggingMessage\|LoggingMessageNotification\|setLoggingLevel\|notifications/message" --include="*.ts" --include="*.js" .
522522

523523
# Check for observability integration
524-
grep -rn "opentelemetry\|datadog\|sentry\|splunk\|elastic-apm" package.json 2>/dev/null
524+
grep -rn "opentelemetry\|datadog\|sentry\|splunk\|elastic-apm" . --include="package.json" 2>/dev/null
525525
```
526526

527527
Compare: count tool registrations (`server.tool` / `@mcp.tool`) vs structured logging references. If tools > 0 and structured logging = 0, flag it.
@@ -538,13 +538,13 @@ Check for floating version references that enable rug-pull attacks.
538538

539539
```bash
540540
# @latest in any MCP config
541-
grep -rniE '"@latest"' .mcp.json claude_desktop_config.json .cursor/mcp.json 2>/dev/null
541+
grep -rniE '"@latest"' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null
542542

543543
# npx without pinned version in MCP config commands
544-
grep -rniE '"command"\s*:\s*"npx"' .mcp.json claude_desktop_config.json 2>/dev/null
544+
grep -rniE '"command"\s*:\s*"npx"' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null
545545

546546
# Verify packages have pinned versions (not @latest)
547-
grep -rniE '@[a-z0-9-]+/[a-z0-9-]+' .mcp.json claude_desktop_config.json 2>/dev/null | grep -v '@[0-9]' | grep -v '@latest'
547+
grep -rniE '@[a-z0-9-]+/[a-z0-9-]+' . --include=".mcp.json" --include="claude_desktop_config.json" 2>/dev/null | grep -v '@[0-9]' | grep -v '@latest'
548548

549549
# Check if any MCP server hashes tool definitions (integrity verification)
550550
grep -rn "createHash\|sha256\|sha-256\|integrity\|checksum" --include="*.ts" --include="*.js" . | grep -iE "(tool|description|schema)"

step-9/step-9-install.sh

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@ verify_prerequisites() {
9494
install_skill() {
9595
SKILL_DIR="$HOME/.claude/skills/safetycheck"
9696
SKILL_FILE="$SKILL_DIR/SKILL.md"
97-
SKILL_URL="https://raw.githubusercontent.com/lorecraft-io/cli-maxxing/main/step-9/safetycheck-skill/SKILL.md"
97+
# Pinned to a specific commit SHA — prevents rug-pull via mutable branch ref
98+
# To update: change the SHA to the new commit and update SKILL_SHA256 to match
99+
SKILL_COMMIT="7b449b652d946a8eef9aca65f0c8e182b4fb80f7"
100+
SKILL_URL="https://raw.githubusercontent.com/lorecraft-io/cli-maxxing/${SKILL_COMMIT}/step-9/safetycheck-skill/SKILL.md"
101+
SKILL_SHA256="77e1ef1127fa35cd860925a652b96dd062ab080d438787b3bde348176597ab12"
98102

99103
info "Creating skill directory..."
100104
mkdir -p "$SKILL_DIR"
@@ -142,6 +146,23 @@ install_skill() {
142146
return
143147
fi
144148

149+
# Verify SHA-256 integrity — protects against corrupted download or tampered content
150+
if command -v shasum &>/dev/null; then
151+
ACTUAL_SHA=$(shasum -a 256 "$SKILL_FILE" | cut -d' ' -f1)
152+
if [ "$ACTUAL_SHA" = "$SKILL_SHA256" ]; then
153+
success "Skill file integrity verified (sha256 match)"
154+
else
155+
soft_fail "Skill file sha256 mismatch — file may be corrupt or tampered. Expected: ${SKILL_SHA256:0:16}..."
156+
fi
157+
elif command -v sha256sum &>/dev/null; then
158+
ACTUAL_SHA=$(sha256sum "$SKILL_FILE" | cut -d' ' -f1)
159+
if [ "$ACTUAL_SHA" = "$SKILL_SHA256" ]; then
160+
success "Skill file integrity verified (sha256 match)"
161+
else
162+
soft_fail "Skill file sha256 mismatch — file may be corrupt or tampered. Expected: ${SKILL_SHA256:0:16}..."
163+
fi
164+
fi
165+
145166
# Verify the file contains expected content
146167
if grep -q "safetycheck" "$SKILL_FILE" 2>/dev/null; then
147168
success "Skill file content verified"

step-final/step-final-install.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,14 +202,14 @@ fi
202202
# =============================================================================
203203
info "Checking for project-level statusLine overrides..."
204204
FOUND_OVERRIDES=0
205-
for PROJECT_SETTINGS in $(find "$HOME/Desktop" "$HOME/Documents" -maxdepth 5 -path "*/.claude/settings.json" -not -path "$HOME/.claude/settings.json" 2>/dev/null); do
205+
while IFS= read -r PROJECT_SETTINGS; do
206206
if command -v jq &>/dev/null && jq -e '.statusLine' "$PROJECT_SETTINGS" &>/dev/null 2>&1; then
207207
jq 'del(.statusLine)' "$PROJECT_SETTINGS" > "${PROJECT_SETTINGS}.tmp" \
208208
&& mv "${PROJECT_SETTINGS}.tmp" "$PROJECT_SETTINGS"
209209
warn "Removed statusLine override from: $PROJECT_SETTINGS"
210210
FOUND_OVERRIDES=$((FOUND_OVERRIDES + 1))
211211
fi
212-
done
212+
done < <(find "$HOME/Desktop" "$HOME/Documents" -maxdepth 5 -path "*/.claude/settings.json" -not -path "$HOME/.claude/settings.json" 2>/dev/null)
213213
if [ "$FOUND_OVERRIDES" -eq 0 ]; then
214214
success "No project-level statusLine overrides found"
215215
else
@@ -234,7 +234,6 @@ esac
234234

235235
HC_PASS=0
236236
HC_FAIL=0
237-
HC_ISSUES=""
238237

239238
# --- Shell aliases ---
240239
for alias_check in \

0 commit comments

Comments
 (0)