gsd-cc 1.3.0 → 1.3.1
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/bin/install.js +22 -10
- package/hooks/gsd-prompt-guard.sh +91 -0
- package/hooks/gsd-statusline.sh +73 -0
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -194,25 +194,31 @@ function installHooks(isGlobal, hooksDir) {
|
|
|
194
194
|
if (!settings.hooks) settings.hooks = {};
|
|
195
195
|
|
|
196
196
|
const boundaryGuard = path.join(hooksDir, 'gsd-boundary-guard.sh');
|
|
197
|
+
const promptGuard = path.join(hooksDir, 'gsd-prompt-guard.sh');
|
|
197
198
|
const contextMonitor = path.join(hooksDir, 'gsd-context-monitor.sh');
|
|
198
199
|
const workflowGuard = path.join(hooksDir, 'gsd-workflow-guard.sh');
|
|
200
|
+
const statusline = path.join(hooksDir, 'gsd-statusline.sh');
|
|
201
|
+
|
|
202
|
+
// Remove all existing GSD-CC hooks before adding (idempotent)
|
|
203
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
204
|
+
settings.hooks[event] = settings.hooks[event].filter(
|
|
205
|
+
h => !JSON.stringify(h).includes('gsd-')
|
|
206
|
+
);
|
|
207
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
208
|
+
}
|
|
199
209
|
|
|
200
|
-
// PreToolUse: boundary guard on Edit/Write
|
|
210
|
+
// PreToolUse: boundary guard + prompt injection guard on Edit/Write
|
|
201
211
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
202
|
-
// Remove existing GSD-CC hooks before adding (idempotent)
|
|
203
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
|
|
204
|
-
h => !JSON.stringify(h).includes('gsd-boundary-guard')
|
|
205
|
-
);
|
|
206
212
|
settings.hooks.PreToolUse.push({
|
|
207
213
|
matcher: 'Edit|Write',
|
|
208
|
-
hooks: [
|
|
214
|
+
hooks: [
|
|
215
|
+
{ type: 'command', command: boundaryGuard, timeout: 5000 },
|
|
216
|
+
{ type: 'command', command: promptGuard, timeout: 5000 }
|
|
217
|
+
]
|
|
209
218
|
});
|
|
210
219
|
|
|
211
|
-
// PostToolUse: context monitor + workflow guard
|
|
220
|
+
// PostToolUse: context monitor (all tools) + workflow guard (Edit/Write)
|
|
212
221
|
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
213
|
-
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(
|
|
214
|
-
h => !JSON.stringify(h).includes('gsd-context-monitor') && !JSON.stringify(h).includes('gsd-workflow-guard')
|
|
215
|
-
);
|
|
216
222
|
settings.hooks.PostToolUse.push({
|
|
217
223
|
hooks: [{ type: 'command', command: contextMonitor, timeout: 5000 }]
|
|
218
224
|
});
|
|
@@ -221,6 +227,12 @@ function installHooks(isGlobal, hooksDir) {
|
|
|
221
227
|
hooks: [{ type: 'command', command: workflowGuard, timeout: 5000 }]
|
|
222
228
|
});
|
|
223
229
|
|
|
230
|
+
// Notification: statusline
|
|
231
|
+
if (!settings.hooks.Notification) settings.hooks.Notification = [];
|
|
232
|
+
settings.hooks.Notification.push({
|
|
233
|
+
hooks: [{ type: 'command', command: statusline, timeout: 3000 }]
|
|
234
|
+
});
|
|
235
|
+
|
|
224
236
|
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
225
237
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
226
238
|
console.log(` ${green}✓${reset} Hooks configured in ${settingsPath.replace(os.homedir(), '~').replace(process.cwd(), '.')}`);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GSD-CC Prompt Injection Guard — PreToolUse hook
|
|
3
|
+
# Scans Write/Edit operations targeting .gsd/ files for prompt injection patterns.
|
|
4
|
+
# Blocks suspicious content from being written into planning artifacts.
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
|
|
8
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd')
|
|
9
|
+
|
|
10
|
+
# Only check Edit and Write operations
|
|
11
|
+
if [ "$TOOL_NAME" != "Edit" ] && [ "$TOOL_NAME" != "Write" ]; then
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
16
|
+
if [ -z "$FILE_PATH" ]; then
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Only scan writes to .gsd/ directory (planning artifacts)
|
|
21
|
+
if [[ "$FILE_PATH" != *".gsd/"* ]]; then
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Get the content being written
|
|
26
|
+
if [ "$TOOL_NAME" = "Write" ]; then
|
|
27
|
+
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty')
|
|
28
|
+
elif [ "$TOOL_NAME" = "Edit" ]; then
|
|
29
|
+
CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty')
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if [ -z "$CONTENT" ]; then
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Check for prompt injection patterns
|
|
37
|
+
SUSPICIOUS=false
|
|
38
|
+
REASON=""
|
|
39
|
+
|
|
40
|
+
# Pattern 1: Direct instruction override attempts
|
|
41
|
+
if echo "$CONTENT" | grep -iqE 'ignore (previous|prior|above|all) (instructions|prompts|rules)'; then
|
|
42
|
+
SUSPICIOUS=true
|
|
43
|
+
REASON="Detected 'ignore previous instructions' pattern"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Pattern 2: Role reassignment
|
|
47
|
+
if echo "$CONTENT" | grep -iqE '(you are now|act as|pretend to be|your new role|forget your|disregard your)'; then
|
|
48
|
+
SUSPICIOUS=true
|
|
49
|
+
REASON="Detected role reassignment pattern"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Pattern 3: System prompt extraction
|
|
53
|
+
if echo "$CONTENT" | grep -iqE '(show|reveal|print|output|display) (your|the|system) (prompt|instructions|rules)'; then
|
|
54
|
+
SUSPICIOUS=true
|
|
55
|
+
REASON="Detected system prompt extraction attempt"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Pattern 4: Invisible Unicode characters (zero-width spaces, RTL override, etc.)
|
|
59
|
+
if echo "$CONTENT" | grep -qP '[\x{200B}\x{200C}\x{200D}\x{FEFF}\x{202A}-\x{202E}\x{2066}-\x{2069}]' 2>/dev/null; then
|
|
60
|
+
SUSPICIOUS=true
|
|
61
|
+
REASON="Detected invisible Unicode characters"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Pattern 5: Base64-encoded instructions
|
|
65
|
+
if echo "$CONTENT" | grep -qE '[A-Za-z0-9+/]{50,}={0,2}'; then
|
|
66
|
+
# Only flag if it's in a suspicious context (not normal code)
|
|
67
|
+
if echo "$CONTENT" | grep -iqE '(decode|eval|execute|base64).*[A-Za-z0-9+/]{50,}'; then
|
|
68
|
+
SUSPICIOUS=true
|
|
69
|
+
REASON="Detected potentially encoded instructions"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Pattern 6: HTML/script injection in markdown
|
|
74
|
+
if echo "$CONTENT" | grep -iqE '<script|javascript:|on(load|error|click)='; then
|
|
75
|
+
SUSPICIOUS=true
|
|
76
|
+
REASON="Detected script injection in planning artifact"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [ "$SUSPICIOUS" = true ]; then
|
|
80
|
+
jq -n --arg reason "$REASON" --arg file "$FILE_PATH" '{
|
|
81
|
+
"hookSpecificOutput": {
|
|
82
|
+
"hookEventName": "PreToolUse",
|
|
83
|
+
"permissionDecision": "deny",
|
|
84
|
+
"permissionDecisionReason": ("PROMPT INJECTION BLOCKED: " + $reason + " in " + $file + ". This content cannot be written to planning artifacts. If this is a false positive, the user can write the file manually.")
|
|
85
|
+
}
|
|
86
|
+
}'
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Content is clean
|
|
91
|
+
exit 0
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# GSD-CC Statusline — Notification hook
|
|
3
|
+
# Renders project status in the terminal statusline.
|
|
4
|
+
# Shows: current phase, milestone/slice/task position, and project type.
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd')
|
|
8
|
+
|
|
9
|
+
# Only render if this is a GSD-CC project
|
|
10
|
+
STATE_FILE="$CWD/.gsd/STATE.md"
|
|
11
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Parse STATE.md frontmatter
|
|
16
|
+
PHASE=$(grep '^phase:' "$STATE_FILE" | head -1 | sed 's/phase: *//')
|
|
17
|
+
MILESTONE=$(grep '^milestone:' "$STATE_FILE" | head -1 | sed 's/milestone: *//')
|
|
18
|
+
SLICE=$(grep '^current_slice:' "$STATE_FILE" | head -1 | sed 's/current_slice: *//')
|
|
19
|
+
TASK=$(grep '^current_task:' "$STATE_FILE" | head -1 | sed 's/current_task: *//')
|
|
20
|
+
RIGOR=$(grep '^rigor:' "$STATE_FILE" | head -1 | sed 's/rigor: *//')
|
|
21
|
+
|
|
22
|
+
# Build position string
|
|
23
|
+
POSITION="$MILESTONE"
|
|
24
|
+
if [ "$SLICE" != "—" ] && [ -n "$SLICE" ]; then
|
|
25
|
+
POSITION="$POSITION / $SLICE"
|
|
26
|
+
fi
|
|
27
|
+
if [ "$TASK" != "—" ] && [ -n "$TASK" ]; then
|
|
28
|
+
POSITION="$POSITION / $TASK"
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Map phase to display name
|
|
32
|
+
case "$PHASE" in
|
|
33
|
+
"seed") PHASE_DISPLAY="Seed" ;;
|
|
34
|
+
"seed-complete") PHASE_DISPLAY="Seed ✓" ;;
|
|
35
|
+
"stack-complete") PHASE_DISPLAY="Stack ✓" ;;
|
|
36
|
+
"roadmap-complete") PHASE_DISPLAY="Roadmap ✓" ;;
|
|
37
|
+
"discuss-complete") PHASE_DISPLAY="Discuss ✓" ;;
|
|
38
|
+
"plan-complete") PHASE_DISPLAY="Plan ✓" ;;
|
|
39
|
+
"planning") PHASE_DISPLAY="Planning..." ;;
|
|
40
|
+
"applying") PHASE_DISPLAY="Executing..." ;;
|
|
41
|
+
"apply-complete") PHASE_DISPLAY="UNIFY required" ;;
|
|
42
|
+
"unified") PHASE_DISPLAY="Unified ✓" ;;
|
|
43
|
+
*) PHASE_DISPLAY="$PHASE" ;;
|
|
44
|
+
esac
|
|
45
|
+
|
|
46
|
+
# Count progress
|
|
47
|
+
TOTAL_SLICES=$(grep -c '| S[0-9]' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
48
|
+
DONE_SLICES=$(grep '| done' "$STATE_FILE" | wc -l | xargs)
|
|
49
|
+
|
|
50
|
+
# Write status to bridge file for other hooks
|
|
51
|
+
BRIDGE_FILE="/tmp/gsd-cc-status-$(echo "$CWD" | md5sum 2>/dev/null | cut -c1-8 || echo "default").json"
|
|
52
|
+
jq -n \
|
|
53
|
+
--arg phase "$PHASE" \
|
|
54
|
+
--arg position "$POSITION" \
|
|
55
|
+
--arg rigor "$RIGOR" \
|
|
56
|
+
--arg total "$TOTAL_SLICES" \
|
|
57
|
+
--arg done "$DONE_SLICES" \
|
|
58
|
+
'{phase: $phase, position: $position, rigor: $rigor, total_slices: ($total|tonumber), done_slices: ($done|tonumber)}' \
|
|
59
|
+
> "$BRIDGE_FILE" 2>/dev/null
|
|
60
|
+
|
|
61
|
+
# Output statusline data
|
|
62
|
+
jq -n \
|
|
63
|
+
--arg pos "$POSITION" \
|
|
64
|
+
--arg phase "$PHASE_DISPLAY" \
|
|
65
|
+
--arg rigor "$RIGOR" \
|
|
66
|
+
--arg progress "${DONE_SLICES}/${TOTAL_SLICES}" \
|
|
67
|
+
'{
|
|
68
|
+
"hookSpecificOutput": {
|
|
69
|
+
"hookEventName": "Notification",
|
|
70
|
+
"additionalContext": ("GSD-CC: " + $pos + " | " + $phase + " | " + $rigor + " | " + $progress + " slices")
|
|
71
|
+
}
|
|
72
|
+
}'
|
|
73
|
+
exit 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-cc",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Get Shit Done on Claude Code — structured AI development with your Max plan",
|
|
5
5
|
"author": "Philipp Briese (https://github.com/0ui-labs)",
|
|
6
6
|
"homepage": "https://github.com/0ui-labs/GSD-CC#readme",
|