oh-my-customcode 0.36.1 → 0.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/.claude/hooks/hooks.json +37 -7
- package/templates/.claude/hooks/scripts/audit-log.sh +55 -0
- package/templates/.claude/hooks/scripts/schema-validator.sh +88 -0
- package/templates/.claude/hooks/scripts/secret-filter.sh +67 -0
- package/templates/.claude/hooks/scripts/session-compliance-report.sh +65 -0
- package/templates/manifest.json +1 -1
package/package.json
CHANGED
|
@@ -72,6 +72,16 @@
|
|
|
72
72
|
],
|
|
73
73
|
"description": "Validate file content hash before Edit — advisory staleness warning"
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
"matcher": "tool == \"Write\" || tool == \"Edit\" || tool == \"Bash\"",
|
|
77
|
+
"hooks": [
|
|
78
|
+
{
|
|
79
|
+
"type": "command",
|
|
80
|
+
"command": "bash .claude/hooks/scripts/schema-validator.sh"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"description": "Schema-based tool input validation — Phase 1 advisory only"
|
|
84
|
+
},
|
|
75
85
|
{
|
|
76
86
|
"matcher": "tool == \"Task\" || tool == \"Agent\"",
|
|
77
87
|
"hooks": [
|
|
@@ -222,6 +232,26 @@
|
|
|
222
232
|
],
|
|
223
233
|
"description": "Context budget advisor — track tool usage patterns and advise ecomode activation"
|
|
224
234
|
},
|
|
235
|
+
{
|
|
236
|
+
"matcher": "tool == \"Edit\" || tool == \"Write\" || tool == \"Bash\" || tool == \"Task\" || tool == \"Agent\"",
|
|
237
|
+
"hooks": [
|
|
238
|
+
{
|
|
239
|
+
"type": "command",
|
|
240
|
+
"command": "bash .claude/hooks/scripts/stuck-detector.sh"
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"description": "Detect repetitive failure loops and advise recovery strategies"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"matcher": "tool == \"Edit\" || tool == \"Write\" || tool == \"Bash\" || tool == \"Task\" || tool == \"Agent\"",
|
|
247
|
+
"hooks": [
|
|
248
|
+
{
|
|
249
|
+
"type": "command",
|
|
250
|
+
"command": "bash .claude/hooks/scripts/cost-cap-advisor.sh"
|
|
251
|
+
}
|
|
252
|
+
],
|
|
253
|
+
"description": "Advisory cost cap monitoring — warn when session cost approaches configurable limit"
|
|
254
|
+
},
|
|
225
255
|
{
|
|
226
256
|
"matcher": "tool == \"Read\"",
|
|
227
257
|
"hooks": [
|
|
@@ -233,24 +263,24 @@
|
|
|
233
263
|
"description": "Store content hashes for Read operations — enables Edit staleness detection"
|
|
234
264
|
},
|
|
235
265
|
{
|
|
236
|
-
"matcher": "tool == \"
|
|
266
|
+
"matcher": "tool == \"Bash\" || tool == \"Read\"",
|
|
237
267
|
"hooks": [
|
|
238
268
|
{
|
|
239
269
|
"type": "command",
|
|
240
|
-
"command": "bash .claude/hooks/scripts/
|
|
270
|
+
"command": "bash .claude/hooks/scripts/secret-filter.sh"
|
|
241
271
|
}
|
|
242
272
|
],
|
|
243
|
-
"description": "Detect
|
|
273
|
+
"description": "Detect potential secrets in Bash/Read output — advisory warning only"
|
|
244
274
|
},
|
|
245
275
|
{
|
|
246
|
-
"matcher": "tool == \"Edit\" || tool == \"Write\" || tool == \"Bash\" || tool == \"
|
|
276
|
+
"matcher": "tool == \"Edit\" || tool == \"Write\" || tool == \"Bash\" || tool == \"Agent\"",
|
|
247
277
|
"hooks": [
|
|
248
278
|
{
|
|
249
279
|
"type": "command",
|
|
250
|
-
"command": "bash .claude/hooks/scripts/
|
|
280
|
+
"command": "bash .claude/hooks/scripts/audit-log.sh"
|
|
251
281
|
}
|
|
252
282
|
],
|
|
253
|
-
"description": "
|
|
283
|
+
"description": "Append-only audit log for state-changing tool operations"
|
|
254
284
|
}
|
|
255
285
|
],
|
|
256
286
|
"Stop": [
|
|
@@ -269,7 +299,7 @@
|
|
|
269
299
|
"hooks": [
|
|
270
300
|
{
|
|
271
301
|
"type": "prompt",
|
|
272
|
-
"prompt": "Session-end memory checkpoint (R011 enforcement). Check conversation history for these
|
|
302
|
+
"prompt": "Session-end memory checkpoint (R011 enforcement). Check conversation history for these 2 steps: 1) sys-memory-keeper was delegated to update MEMORY.md 2) claude-mem save was attempted via ToolSearch + mcp__plugin_claude-mem_mcp-search__save_memory. Note: episodic-memory auto-indexes after session — no manual verification needed. Decision rules: If BOTH were attempted (success or failure both count): approve. If MCP tools are unavailable after ToolSearch attempt: approve with note. If session had no explicit session-end signal from user (quick question, no memory work): approve. If any step was NOT attempted despite user signaling session end: block with systemMessage listing the missing steps."
|
|
273
303
|
}
|
|
274
304
|
],
|
|
275
305
|
"description": "Enforce R011 session-end memory saves — block stop if claude-mem or episodic-memory saves were skipped"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Audit Log Hook — Append-only JSONL persistence
|
|
3
|
+
# Trigger: PostToolUse on Edit, Write, Bash, Agent
|
|
4
|
+
# Purpose: Persistent audit trail for security and compliance
|
|
5
|
+
# Protocol: stdin JSON -> log entry -> stdout pass-through
|
|
6
|
+
# Always exits 0 (advisory only)
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
input=$(cat)
|
|
11
|
+
|
|
12
|
+
# Extract fields from hook input
|
|
13
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // "unknown"')
|
|
14
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.command // ""' | head -c 200)
|
|
15
|
+
agent_type=$(echo "$input" | jq -r '.agent_type // "unknown"')
|
|
16
|
+
model=$(echo "$input" | jq -r '.model // "unknown"')
|
|
17
|
+
is_error=$(echo "$input" | jq -r '.tool_output.is_error // false')
|
|
18
|
+
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
19
|
+
|
|
20
|
+
# Determine outcome
|
|
21
|
+
if [ "$is_error" = "true" ]; then
|
|
22
|
+
outcome="error"
|
|
23
|
+
else
|
|
24
|
+
outcome="success"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Audit log location
|
|
28
|
+
AUDIT_LOG="${HOME}/.claude/audit.jsonl"
|
|
29
|
+
|
|
30
|
+
# Ensure directory exists
|
|
31
|
+
mkdir -p "$(dirname "$AUDIT_LOG")"
|
|
32
|
+
|
|
33
|
+
# Write audit entry (append-only JSONL)
|
|
34
|
+
jq -cn \
|
|
35
|
+
--arg ts "$timestamp" \
|
|
36
|
+
--arg tool "$tool_name" \
|
|
37
|
+
--arg path "$file_path" \
|
|
38
|
+
--arg agent "$agent_type" \
|
|
39
|
+
--arg model "$model" \
|
|
40
|
+
--arg outcome "$outcome" \
|
|
41
|
+
--arg ppid "${PPID}" \
|
|
42
|
+
'{timestamp: $ts, tool: $tool, path: $path, agent_type: $agent, model: $model, outcome: $outcome, session_ppid: $ppid}' \
|
|
43
|
+
>> "$AUDIT_LOG" 2>/dev/null || true
|
|
44
|
+
|
|
45
|
+
# Daily rotation check (rotate if > 10MB)
|
|
46
|
+
if [ -f "$AUDIT_LOG" ]; then
|
|
47
|
+
file_size=$(stat -f%z "$AUDIT_LOG" 2>/dev/null || stat -c%s "$AUDIT_LOG" 2>/dev/null || echo "0")
|
|
48
|
+
if [ "$file_size" -gt 10485760 ]; then
|
|
49
|
+
mv "$AUDIT_LOG" "${AUDIT_LOG}.$(date -u +%Y%m%d%H%M%S)" 2>/dev/null || true
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Pass through
|
|
54
|
+
echo "$input"
|
|
55
|
+
exit 0
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Schema Validator Hook — PreToolUse input validation
|
|
3
|
+
# Trigger: PreToolUse on Write, Edit, Bash
|
|
4
|
+
# Purpose: Validate tool inputs against JSON Schema definitions
|
|
5
|
+
# Phase 1: Advisory only (exit 0 with stderr warning)
|
|
6
|
+
# Protocol: stdin JSON -> validate -> stdout pass-through
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
input=$(cat)
|
|
11
|
+
|
|
12
|
+
# Extract tool info
|
|
13
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // "unknown"')
|
|
14
|
+
tool_input=$(echo "$input" | jq -r '.tool_input // {}')
|
|
15
|
+
|
|
16
|
+
SCHEMA_FILE=".claude/schemas/tool-inputs.json"
|
|
17
|
+
|
|
18
|
+
# Skip if schema file doesn't exist
|
|
19
|
+
if [ ! -f "$SCHEMA_FILE" ]; then
|
|
20
|
+
echo "$input"
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
warnings=()
|
|
25
|
+
|
|
26
|
+
case "$tool_name" in
|
|
27
|
+
"Write")
|
|
28
|
+
file_path=$(echo "$tool_input" | jq -r '.file_path // ""')
|
|
29
|
+
content=$(echo "$tool_input" | jq -r '.content // ""')
|
|
30
|
+
|
|
31
|
+
if [ -z "$file_path" ]; then
|
|
32
|
+
warnings+=("[Schema] Write: file_path is empty or missing")
|
|
33
|
+
fi
|
|
34
|
+
if [ -z "$content" ]; then
|
|
35
|
+
warnings+=("[Schema] Write: content is empty — creating empty file?")
|
|
36
|
+
fi
|
|
37
|
+
;;
|
|
38
|
+
|
|
39
|
+
"Edit")
|
|
40
|
+
file_path=$(echo "$tool_input" | jq -r '.file_path // ""')
|
|
41
|
+
old_string=$(echo "$tool_input" | jq -r '.old_string // ""')
|
|
42
|
+
new_string=$(echo "$tool_input" | jq -r '.new_string // ""')
|
|
43
|
+
|
|
44
|
+
if [ -z "$file_path" ]; then
|
|
45
|
+
warnings+=("[Schema] Edit: file_path is empty or missing")
|
|
46
|
+
fi
|
|
47
|
+
if [ -z "$old_string" ]; then
|
|
48
|
+
warnings+=("[Schema] Edit: old_string is empty")
|
|
49
|
+
fi
|
|
50
|
+
if [ "$old_string" = "$new_string" ]; then
|
|
51
|
+
warnings+=("[Schema] Edit: old_string equals new_string — no-op edit")
|
|
52
|
+
fi
|
|
53
|
+
;;
|
|
54
|
+
|
|
55
|
+
"Bash")
|
|
56
|
+
command=$(echo "$tool_input" | jq -r '.command // ""')
|
|
57
|
+
|
|
58
|
+
if [ -z "$command" ]; then
|
|
59
|
+
warnings+=("[Schema] Bash: command is empty")
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Check dangerous patterns
|
|
63
|
+
if echo "$command" | grep -qE 'rm\s+-rf\s+/[^.]'; then
|
|
64
|
+
warnings+=("[Schema] Bash: DANGER — recursive delete from root detected")
|
|
65
|
+
fi
|
|
66
|
+
if echo "$command" | grep -qE '^\s*sudo\s+'; then
|
|
67
|
+
warnings+=("[Schema] Bash: elevated privilege command detected")
|
|
68
|
+
fi
|
|
69
|
+
if echo "$command" | grep -qE '> /dev/sd'; then
|
|
70
|
+
warnings+=("[Schema] Bash: direct disk write detected")
|
|
71
|
+
fi
|
|
72
|
+
if echo "$command" | grep -qE 'mkfs\.'; then
|
|
73
|
+
warnings+=("[Schema] Bash: filesystem format command detected")
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
# Output warnings (advisory only)
|
|
79
|
+
if [ ${#warnings[@]} -gt 0 ]; then
|
|
80
|
+
for w in "${warnings[@]}"; do
|
|
81
|
+
echo "$w" >&2
|
|
82
|
+
done
|
|
83
|
+
echo "[Schema] Phase 1: advisory only — not blocking" >&2
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Always pass through (Phase 1)
|
|
87
|
+
echo "$input"
|
|
88
|
+
exit 0
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Secret Output Filter Hook — Detect potential secrets in tool output
|
|
3
|
+
# Trigger: PostToolUse on Bash, Read
|
|
4
|
+
# Purpose: Advisory warning when potential secrets detected in output
|
|
5
|
+
# Protocol: stdin JSON -> scan -> stdout pass-through
|
|
6
|
+
# Always exits 0 (advisory only, never blocks)
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
input=$(cat)
|
|
11
|
+
|
|
12
|
+
# Extract output to scan
|
|
13
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // "unknown"')
|
|
14
|
+
output=$(echo "$input" | jq -r '.tool_output.output // ""')
|
|
15
|
+
|
|
16
|
+
# Skip if no output
|
|
17
|
+
if [ -z "$output" ] || [ "$output" = "null" ]; then
|
|
18
|
+
echo "$input"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Secret patterns to detect
|
|
23
|
+
detected=false
|
|
24
|
+
|
|
25
|
+
# AWS Access Key ID
|
|
26
|
+
if echo "$output" | grep -qE 'AKIA[0-9A-Z]{16}'; then
|
|
27
|
+
echo "[Security] Potential AWS Access Key detected in ${tool_name} output" >&2
|
|
28
|
+
detected=true
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# OpenAI/Anthropic API Key
|
|
32
|
+
if echo "$output" | grep -qE 'sk-[a-zA-Z0-9]{32,}'; then
|
|
33
|
+
echo "[Security] Potential API key (sk-*) detected in ${tool_name} output" >&2
|
|
34
|
+
detected=true
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# GitHub Personal Access Token
|
|
38
|
+
if echo "$output" | grep -qE 'ghp_[a-zA-Z0-9]{36}'; then
|
|
39
|
+
echo "[Security] Potential GitHub PAT detected in ${tool_name} output" >&2
|
|
40
|
+
detected=true
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Private Key
|
|
44
|
+
if echo "$output" | grep -qE '-----BEGIN.*PRIVATE KEY-----'; then
|
|
45
|
+
echo "[Security] Potential private key detected in ${tool_name} output" >&2
|
|
46
|
+
detected=true
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Bearer Token (long)
|
|
50
|
+
if echo "$output" | grep -qE 'Bearer [a-zA-Z0-9._-]{20,}'; then
|
|
51
|
+
echo "[Security] Potential Bearer token detected in ${tool_name} output" >&2
|
|
52
|
+
detected=true
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# GitHub OAuth Token
|
|
56
|
+
if echo "$output" | grep -qE 'gho_[a-zA-Z0-9]{36}'; then
|
|
57
|
+
echo "[Security] Potential GitHub OAuth token detected in ${tool_name} output" >&2
|
|
58
|
+
detected=true
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
if [ "$detected" = true ]; then
|
|
62
|
+
echo "[Security] Review output carefully — do NOT commit or expose secrets" >&2
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Pass through (always)
|
|
66
|
+
echo "$input"
|
|
67
|
+
exit 0
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Stop hook: Session compliance report (R265 Phase 1)
|
|
3
|
+
# Reads violation logs collected by PreToolUse hooks during the session
|
|
4
|
+
# Advisory only — never blocks session termination
|
|
5
|
+
# Ref: https://github.com/baekenough/oh-my-customcode/issues/265
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
input=$(cat)
|
|
10
|
+
|
|
11
|
+
VIOLATIONS_FILE="/tmp/.claude-violations-${PPID}"
|
|
12
|
+
TASK_COUNT_FILE="/tmp/.claude-task-count-${PPID}"
|
|
13
|
+
|
|
14
|
+
echo "" >&2
|
|
15
|
+
echo "╔══════════════════════════════════════════════╗" >&2
|
|
16
|
+
echo "║ Session Compliance Report ║" >&2
|
|
17
|
+
echo "╚══════════════════════════════════════════════╝" >&2
|
|
18
|
+
|
|
19
|
+
# Count total Agent/Task calls
|
|
20
|
+
if [ -f "$TASK_COUNT_FILE" ]; then
|
|
21
|
+
TOTAL_TASKS=$(cat "$TASK_COUNT_FILE")
|
|
22
|
+
echo "[Compliance] Agent/Task calls this session: ${TOTAL_TASKS}" >&2
|
|
23
|
+
else
|
|
24
|
+
TOTAL_TASKS=0
|
|
25
|
+
echo "[Compliance] Agent/Task calls this session: 0" >&2
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Check violations
|
|
29
|
+
if [ -f "$VIOLATIONS_FILE" ] && [ -s "$VIOLATIONS_FILE" ]; then
|
|
30
|
+
VIOLATION_COUNT=$(wc -l < "$VIOLATIONS_FILE" | tr -d ' ')
|
|
31
|
+
echo "[Compliance] Violations detected: ${VIOLATION_COUNT}" >&2
|
|
32
|
+
echo "" >&2
|
|
33
|
+
|
|
34
|
+
# Group by rule
|
|
35
|
+
R010_COUNT=$(grep -c '"rule":"R010"' "$VIOLATIONS_FILE" 2>/dev/null || echo "0")
|
|
36
|
+
R018_COUNT=$(grep -c '"rule":"R018"' "$VIOLATIONS_FILE" 2>/dev/null || echo "0")
|
|
37
|
+
|
|
38
|
+
if [ "$R010_COUNT" -gt 0 ]; then
|
|
39
|
+
echo " R010 (Git Delegation): ${R010_COUNT} violation(s)" >&2
|
|
40
|
+
grep '"rule":"R010"' "$VIOLATIONS_FILE" | jq -r '.detail' 2>/dev/null | while read -r detail; do
|
|
41
|
+
echo " - ${detail}" >&2
|
|
42
|
+
done
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [ "$R018_COUNT" -gt 0 ]; then
|
|
46
|
+
echo " R018 (Agent Teams): ${R018_COUNT} violation(s)" >&2
|
|
47
|
+
grep '"rule":"R018"' "$VIOLATIONS_FILE" | jq -r '.detail' 2>/dev/null | while read -r detail; do
|
|
48
|
+
echo " - ${detail}" >&2
|
|
49
|
+
done
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo "" >&2
|
|
53
|
+
echo "[Compliance] Review violations above and consider rule updates per R016." >&2
|
|
54
|
+
else
|
|
55
|
+
echo "[Compliance] No violations detected. All clear!" >&2
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "────────────────────────────────────────────────" >&2
|
|
59
|
+
|
|
60
|
+
# Cleanup temp files (best effort)
|
|
61
|
+
rm -f "$VIOLATIONS_FILE" 2>/dev/null || true
|
|
62
|
+
|
|
63
|
+
# CRITICAL: Always pass through input and exit 0
|
|
64
|
+
echo "$input"
|
|
65
|
+
exit 0
|
package/templates/manifest.json
CHANGED