gsd-code-first 1.0.0
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/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +937 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/gsd-advisor-researcher.md +104 -0
- package/agents/gsd-annotator.md +148 -0
- package/agents/gsd-arc-executor.md +537 -0
- package/agents/gsd-arc-planner.md +374 -0
- package/agents/gsd-assumptions-analyzer.md +105 -0
- package/agents/gsd-code-planner.md +155 -0
- package/agents/gsd-codebase-mapper.md +770 -0
- package/agents/gsd-debugger.md +1373 -0
- package/agents/gsd-executor.md +509 -0
- package/agents/gsd-integration-checker.md +443 -0
- package/agents/gsd-nyquist-auditor.md +176 -0
- package/agents/gsd-phase-researcher.md +698 -0
- package/agents/gsd-plan-checker.md +773 -0
- package/agents/gsd-planner.md +1354 -0
- package/agents/gsd-project-researcher.md +654 -0
- package/agents/gsd-prototyper.md +161 -0
- package/agents/gsd-research-synthesizer.md +247 -0
- package/agents/gsd-roadmapper.md +679 -0
- package/agents/gsd-ui-auditor.md +439 -0
- package/agents/gsd-ui-checker.md +300 -0
- package/agents/gsd-ui-researcher.md +357 -0
- package/agents/gsd-user-profiler.md +171 -0
- package/agents/gsd-verifier.md +700 -0
- package/bin/install.js +5009 -0
- package/commands/gsd/add-backlog.md +76 -0
- package/commands/gsd/add-phase.md +43 -0
- package/commands/gsd/add-tests.md +41 -0
- package/commands/gsd/add-todo.md +47 -0
- package/commands/gsd/annotate.md +54 -0
- package/commands/gsd/audit-milestone.md +36 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +41 -0
- package/commands/gsd/check-todos.md +45 -0
- package/commands/gsd/cleanup.md +18 -0
- package/commands/gsd/complete-milestone.md +136 -0
- package/commands/gsd/debug.md +173 -0
- package/commands/gsd/deep-plan.md +52 -0
- package/commands/gsd/discuss-phase.md +64 -0
- package/commands/gsd/do.md +30 -0
- package/commands/gsd/execute-phase.md +59 -0
- package/commands/gsd/extract-plan.md +35 -0
- package/commands/gsd/fast.md +30 -0
- package/commands/gsd/forensics.md +56 -0
- package/commands/gsd/health.md +22 -0
- package/commands/gsd/help.md +22 -0
- package/commands/gsd/insert-phase.md +32 -0
- package/commands/gsd/iterate.md +124 -0
- package/commands/gsd/join-discord.md +18 -0
- package/commands/gsd/list-phase-assumptions.md +46 -0
- package/commands/gsd/list-workspaces.md +19 -0
- package/commands/gsd/manager.md +39 -0
- package/commands/gsd/map-codebase.md +71 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/new-milestone.md +44 -0
- package/commands/gsd/new-project.md +42 -0
- package/commands/gsd/new-workspace.md +44 -0
- package/commands/gsd/next.md +24 -0
- package/commands/gsd/note.md +34 -0
- package/commands/gsd/pause-work.md +38 -0
- package/commands/gsd/plan-milestone-gaps.md +34 -0
- package/commands/gsd/plan-phase.md +47 -0
- package/commands/gsd/plant-seed.md +28 -0
- package/commands/gsd/pr-branch.md +25 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +24 -0
- package/commands/gsd/prototype.md +56 -0
- package/commands/gsd/quick.md +47 -0
- package/commands/gsd/reapply-patches.md +123 -0
- package/commands/gsd/remove-phase.md +31 -0
- package/commands/gsd/remove-workspace.md +26 -0
- package/commands/gsd/research-phase.md +195 -0
- package/commands/gsd/resume-work.md +40 -0
- package/commands/gsd/review-backlog.md +61 -0
- package/commands/gsd/review.md +37 -0
- package/commands/gsd/session-report.md +19 -0
- package/commands/gsd/set-mode.md +41 -0
- package/commands/gsd/set-profile.md +12 -0
- package/commands/gsd/settings.md +36 -0
- package/commands/gsd/ship.md +23 -0
- package/commands/gsd/stats.md +18 -0
- package/commands/gsd/thread.md +127 -0
- package/commands/gsd/ui-phase.md +34 -0
- package/commands/gsd/ui-review.md +32 -0
- package/commands/gsd/update.md +37 -0
- package/commands/gsd/validate-phase.md +35 -0
- package/commands/gsd/verify-work.md +38 -0
- package/commands/gsd/workstreams.md +63 -0
- package/get-shit-done/bin/gsd-tools.cjs +946 -0
- package/get-shit-done/bin/lib/arc-scanner.cjs +341 -0
- package/get-shit-done/bin/lib/commands.cjs +959 -0
- package/get-shit-done/bin/lib/config.cjs +466 -0
- package/get-shit-done/bin/lib/core.cjs +1230 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +336 -0
- package/get-shit-done/bin/lib/init.cjs +1442 -0
- package/get-shit-done/bin/lib/milestone.cjs +252 -0
- package/get-shit-done/bin/lib/model-profiles.cjs +68 -0
- package/get-shit-done/bin/lib/phase.cjs +888 -0
- package/get-shit-done/bin/lib/profile-output.cjs +952 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/roadmap.cjs +329 -0
- package/get-shit-done/bin/lib/security.cjs +382 -0
- package/get-shit-done/bin/lib/state.cjs +1031 -0
- package/get-shit-done/bin/lib/template.cjs +222 -0
- package/get-shit-done/bin/lib/uat.cjs +282 -0
- package/get-shit-done/bin/lib/verify.cjs +888 -0
- package/get-shit-done/bin/lib/workstream.cjs +491 -0
- package/get-shit-done/commands/gsd/workstreams.md +63 -0
- package/get-shit-done/references/arc-standard.md +315 -0
- package/get-shit-done/references/checkpoints.md +778 -0
- package/get-shit-done/references/continuation-format.md +249 -0
- package/get-shit-done/references/decimal-phase-calculation.md +64 -0
- package/get-shit-done/references/git-integration.md +295 -0
- package/get-shit-done/references/git-planning-commit.md +38 -0
- package/get-shit-done/references/model-profile-resolution.md +36 -0
- package/get-shit-done/references/model-profiles.md +139 -0
- package/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/get-shit-done/references/planning-config.md +202 -0
- package/get-shit-done/references/questioning.md +162 -0
- package/get-shit-done/references/tdd.md +263 -0
- package/get-shit-done/references/ui-brand.md +160 -0
- package/get-shit-done/references/user-profiling.md +681 -0
- package/get-shit-done/references/verification-patterns.md +612 -0
- package/get-shit-done/references/workstream-flag.md +58 -0
- package/get-shit-done/templates/DEBUG.md +164 -0
- package/get-shit-done/templates/UAT.md +265 -0
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +76 -0
- package/get-shit-done/templates/claude-md.md +122 -0
- package/get-shit-done/templates/codebase/architecture.md +255 -0
- package/get-shit-done/templates/codebase/concerns.md +310 -0
- package/get-shit-done/templates/codebase/conventions.md +307 -0
- package/get-shit-done/templates/codebase/integrations.md +280 -0
- package/get-shit-done/templates/codebase/stack.md +186 -0
- package/get-shit-done/templates/codebase/structure.md +285 -0
- package/get-shit-done/templates/codebase/testing.md +480 -0
- package/get-shit-done/templates/config.json +44 -0
- package/get-shit-done/templates/context.md +352 -0
- package/get-shit-done/templates/continue-here.md +78 -0
- package/get-shit-done/templates/copilot-instructions.md +7 -0
- package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/get-shit-done/templates/dev-preferences.md +21 -0
- package/get-shit-done/templates/discovery.md +146 -0
- package/get-shit-done/templates/discussion-log.md +63 -0
- package/get-shit-done/templates/milestone-archive.md +123 -0
- package/get-shit-done/templates/milestone.md +115 -0
- package/get-shit-done/templates/phase-prompt.md +610 -0
- package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/get-shit-done/templates/project.md +186 -0
- package/get-shit-done/templates/requirements.md +231 -0
- package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/get-shit-done/templates/research-project/STACK.md +120 -0
- package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/get-shit-done/templates/research.md +552 -0
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +202 -0
- package/get-shit-done/templates/state.md +176 -0
- package/get-shit-done/templates/summary-complex.md +59 -0
- package/get-shit-done/templates/summary-minimal.md +41 -0
- package/get-shit-done/templates/summary-standard.md +48 -0
- package/get-shit-done/templates/summary.md +248 -0
- package/get-shit-done/templates/user-profile.md +146 -0
- package/get-shit-done/templates/user-setup.md +311 -0
- package/get-shit-done/templates/verification-report.md +322 -0
- package/get-shit-done/workflows/add-phase.md +112 -0
- package/get-shit-done/workflows/add-tests.md +351 -0
- package/get-shit-done/workflows/add-todo.md +158 -0
- package/get-shit-done/workflows/audit-milestone.md +340 -0
- package/get-shit-done/workflows/audit-uat.md +109 -0
- package/get-shit-done/workflows/autonomous.md +891 -0
- package/get-shit-done/workflows/check-todos.md +177 -0
- package/get-shit-done/workflows/cleanup.md +152 -0
- package/get-shit-done/workflows/complete-milestone.md +767 -0
- package/get-shit-done/workflows/diagnose-issues.md +231 -0
- package/get-shit-done/workflows/discovery-phase.md +289 -0
- package/get-shit-done/workflows/discuss-phase-assumptions.md +653 -0
- package/get-shit-done/workflows/discuss-phase.md +1049 -0
- package/get-shit-done/workflows/do.md +104 -0
- package/get-shit-done/workflows/execute-phase.md +846 -0
- package/get-shit-done/workflows/execute-plan.md +514 -0
- package/get-shit-done/workflows/fast.md +105 -0
- package/get-shit-done/workflows/forensics.md +265 -0
- package/get-shit-done/workflows/health.md +181 -0
- package/get-shit-done/workflows/help.md +634 -0
- package/get-shit-done/workflows/insert-phase.md +130 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/list-workspaces.md +56 -0
- package/get-shit-done/workflows/manager.md +362 -0
- package/get-shit-done/workflows/map-codebase.md +377 -0
- package/get-shit-done/workflows/milestone-summary.md +223 -0
- package/get-shit-done/workflows/new-milestone.md +486 -0
- package/get-shit-done/workflows/new-project.md +1250 -0
- package/get-shit-done/workflows/new-workspace.md +237 -0
- package/get-shit-done/workflows/next.md +97 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/note.md +156 -0
- package/get-shit-done/workflows/pause-work.md +176 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
- package/get-shit-done/workflows/plan-phase.md +859 -0
- package/get-shit-done/workflows/plant-seed.md +169 -0
- package/get-shit-done/workflows/pr-branch.md +129 -0
- package/get-shit-done/workflows/profile-user.md +450 -0
- package/get-shit-done/workflows/progress.md +507 -0
- package/get-shit-done/workflows/quick.md +757 -0
- package/get-shit-done/workflows/remove-phase.md +155 -0
- package/get-shit-done/workflows/remove-workspace.md +90 -0
- package/get-shit-done/workflows/research-phase.md +82 -0
- package/get-shit-done/workflows/resume-project.md +326 -0
- package/get-shit-done/workflows/review.md +228 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/settings.md +283 -0
- package/get-shit-done/workflows/ship.md +228 -0
- package/get-shit-done/workflows/stats.md +60 -0
- package/get-shit-done/workflows/transition.md +671 -0
- package/get-shit-done/workflows/ui-phase.md +302 -0
- package/get-shit-done/workflows/ui-review.md +165 -0
- package/get-shit-done/workflows/update.md +323 -0
- package/get-shit-done/workflows/validate-phase.md +174 -0
- package/get-shit-done/workflows/verify-phase.md +254 -0
- package/get-shit-done/workflows/verify-work.md +637 -0
- package/hooks/dist/gsd-check-update.js +114 -0
- package/hooks/dist/gsd-context-monitor.js +156 -0
- package/hooks/dist/gsd-prompt-guard.js +96 -0
- package/hooks/dist/gsd-statusline.js +119 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/package.json +52 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +82 -0
- package/scripts/prompt-injection-scan.sh +198 -0
- package/scripts/run-tests.cjs +29 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# base64-scan.sh — Detect base64-obfuscated prompt injection in source files
|
|
3
|
+
#
|
|
4
|
+
# Extracts base64 blobs >= 40 chars, decodes them, and checks decoded content
|
|
5
|
+
# against the same injection patterns used by prompt-injection-scan.sh.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# scripts/base64-scan.sh --diff origin/main # CI mode: scan changed files
|
|
9
|
+
# scripts/base64-scan.sh --file path/to/file # Scan a single file
|
|
10
|
+
# scripts/base64-scan.sh --dir agents/ # Scan all files in a directory
|
|
11
|
+
#
|
|
12
|
+
# Exit codes:
|
|
13
|
+
# 0 = clean
|
|
14
|
+
# 1 = findings detected
|
|
15
|
+
# 2 = usage error
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
MIN_BLOB_LENGTH=40
|
|
20
|
+
|
|
21
|
+
# ─── Injection Patterns (decoded content) ────────────────────────────────────
|
|
22
|
+
# Subset of patterns — if someone base64-encoded something, check for the
|
|
23
|
+
# most common injection indicators.
|
|
24
|
+
DECODED_PATTERNS=(
|
|
25
|
+
'ignore[[:space:]]+(all[[:space:]]+)?previous[[:space:]]+instructions'
|
|
26
|
+
'you[[:space:]]+are[[:space:]]+now[[:space:]]+'
|
|
27
|
+
'system[[:space:]]+prompt'
|
|
28
|
+
'</?system>'
|
|
29
|
+
'</?assistant>'
|
|
30
|
+
'\[SYSTEM\]'
|
|
31
|
+
'\[INST\]'
|
|
32
|
+
'<<SYS>>'
|
|
33
|
+
'override[[:space:]]+(system|safety|security)'
|
|
34
|
+
'pretend[[:space:]]+(you|to)[[:space:]]'
|
|
35
|
+
'act[[:space:]]+as[[:space:]]+(a|an|if)'
|
|
36
|
+
'jailbreak'
|
|
37
|
+
'bypass[[:space:]]+(safety|content|security)'
|
|
38
|
+
'eval[[:space:]]*\('
|
|
39
|
+
'exec[[:space:]]*\('
|
|
40
|
+
'rm[[:space:]]+-rf'
|
|
41
|
+
'curl[[:space:]].*\|[[:space:]]*sh'
|
|
42
|
+
'wget[[:space:]].*\|[[:space:]]*sh'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# ─── Ignorelist ──────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
IGNOREFILE=".base64scanignore"
|
|
48
|
+
IGNORED_PATTERNS=()
|
|
49
|
+
|
|
50
|
+
load_ignorelist() {
|
|
51
|
+
if [[ -f "$IGNOREFILE" ]]; then
|
|
52
|
+
while IFS= read -r line; do
|
|
53
|
+
# Skip comments and empty lines
|
|
54
|
+
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
55
|
+
[[ -z "${line// }" ]] && continue
|
|
56
|
+
IGNORED_PATTERNS+=("$line")
|
|
57
|
+
done < "$IGNOREFILE"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
is_ignored() {
|
|
62
|
+
local blob="$1"
|
|
63
|
+
if [[ ${#IGNORED_PATTERNS[@]} -eq 0 ]]; then
|
|
64
|
+
return 1
|
|
65
|
+
fi
|
|
66
|
+
for pattern in "${IGNORED_PATTERNS[@]}"; do
|
|
67
|
+
if [[ "$blob" == "$pattern" ]]; then
|
|
68
|
+
return 0
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
return 1
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ─── Skip Rules ──────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
should_skip_file() {
|
|
77
|
+
local file="$1"
|
|
78
|
+
# Skip binary files
|
|
79
|
+
case "$file" in
|
|
80
|
+
*.png|*.jpg|*.jpeg|*.gif|*.ico|*.woff|*.woff2|*.ttf|*.eot|*.otf) return 0 ;;
|
|
81
|
+
*.zip|*.tar|*.gz|*.bz2|*.xz|*.7z) return 0 ;;
|
|
82
|
+
*.pdf|*.doc|*.docx|*.xls|*.xlsx) return 0 ;;
|
|
83
|
+
esac
|
|
84
|
+
# Skip lockfiles and node_modules
|
|
85
|
+
case "$file" in
|
|
86
|
+
*/node_modules/*) return 0 ;;
|
|
87
|
+
*/package-lock.json) return 0 ;;
|
|
88
|
+
*/yarn.lock) return 0 ;;
|
|
89
|
+
*/pnpm-lock.yaml) return 0 ;;
|
|
90
|
+
esac
|
|
91
|
+
# Skip the scan scripts themselves and test files
|
|
92
|
+
case "$file" in
|
|
93
|
+
*/base64-scan.sh) return 0 ;;
|
|
94
|
+
*/security-scan.test.cjs) return 0 ;;
|
|
95
|
+
esac
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
is_data_uri() {
|
|
100
|
+
local context="$1"
|
|
101
|
+
# data:image/png;base64,... or data:application/font-woff;base64,...
|
|
102
|
+
echo "$context" | grep -qE 'data:[a-zA-Z]+/[a-zA-Z0-9.+-]+;base64,' 2>/dev/null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# ─── File Collection ─────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
collect_files() {
|
|
108
|
+
local mode="$1"
|
|
109
|
+
shift
|
|
110
|
+
|
|
111
|
+
case "$mode" in
|
|
112
|
+
--diff)
|
|
113
|
+
local base="${1:-origin/main}"
|
|
114
|
+
git diff --name-only --diff-filter=ACMR "$base"...HEAD 2>/dev/null \
|
|
115
|
+
| grep -vE '\.(png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|otf|zip|tar|gz|pdf)$' || true
|
|
116
|
+
;;
|
|
117
|
+
--file)
|
|
118
|
+
if [[ -f "$1" ]]; then
|
|
119
|
+
echo "$1"
|
|
120
|
+
else
|
|
121
|
+
echo "Error: file not found: $1" >&2
|
|
122
|
+
exit 2
|
|
123
|
+
fi
|
|
124
|
+
;;
|
|
125
|
+
--dir)
|
|
126
|
+
local dir="$1"
|
|
127
|
+
if [[ ! -d "$dir" ]]; then
|
|
128
|
+
echo "Error: directory not found: $dir" >&2
|
|
129
|
+
exit 2
|
|
130
|
+
fi
|
|
131
|
+
find "$dir" -type f ! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/dist/*' \
|
|
132
|
+
! -name '*.png' ! -name '*.jpg' ! -name '*.gif' ! -name '*.woff*' 2>/dev/null || true
|
|
133
|
+
;;
|
|
134
|
+
--stdin)
|
|
135
|
+
cat
|
|
136
|
+
;;
|
|
137
|
+
*)
|
|
138
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path> | --stdin" >&2
|
|
139
|
+
exit 2
|
|
140
|
+
;;
|
|
141
|
+
esac
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# ─── Scanner ─────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
extract_and_check_blobs() {
|
|
147
|
+
local file="$1"
|
|
148
|
+
local found=0
|
|
149
|
+
local line_num=0
|
|
150
|
+
|
|
151
|
+
while IFS= read -r line; do
|
|
152
|
+
line_num=$((line_num + 1))
|
|
153
|
+
|
|
154
|
+
# Skip data URIs — legitimate base64 usage
|
|
155
|
+
if is_data_uri "$line"; then
|
|
156
|
+
continue
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
# Extract base64-like blobs (alphanumeric + / + = padding, >= MIN_BLOB_LENGTH)
|
|
160
|
+
local blobs
|
|
161
|
+
blobs=$(echo "$line" | grep -oE '[A-Za-z0-9+/]{'"$MIN_BLOB_LENGTH"',}={0,3}' 2>/dev/null || true)
|
|
162
|
+
|
|
163
|
+
if [[ -z "$blobs" ]]; then
|
|
164
|
+
continue
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
while IFS= read -r blob; do
|
|
168
|
+
[[ -z "$blob" ]] && continue
|
|
169
|
+
|
|
170
|
+
# Check ignorelist
|
|
171
|
+
if [[ ${#IGNORED_PATTERNS[@]} -gt 0 ]] && is_ignored "$blob"; then
|
|
172
|
+
continue
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# Try to decode — if it fails, not valid base64
|
|
176
|
+
local decoded
|
|
177
|
+
decoded=$(echo "$blob" | base64 -d 2>/dev/null || echo "")
|
|
178
|
+
|
|
179
|
+
if [[ -z "$decoded" ]]; then
|
|
180
|
+
continue
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Check if decoded content is mostly printable text (not random binary)
|
|
184
|
+
local printable_ratio
|
|
185
|
+
local total_chars=${#decoded}
|
|
186
|
+
if [[ $total_chars -eq 0 ]]; then
|
|
187
|
+
continue
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# Count printable ASCII characters
|
|
191
|
+
local printable_count
|
|
192
|
+
printable_count=$(echo -n "$decoded" | tr -cd '[:print:]' | wc -c | tr -d ' ')
|
|
193
|
+
# Skip if less than 70% printable (likely binary data, not obfuscated text)
|
|
194
|
+
if [[ $((printable_count * 100 / total_chars)) -lt 70 ]]; then
|
|
195
|
+
continue
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Scan decoded content against injection patterns
|
|
199
|
+
for pattern in "${DECODED_PATTERNS[@]}"; do
|
|
200
|
+
if echo "$decoded" | grep -iqE "$pattern" 2>/dev/null; then
|
|
201
|
+
if [[ $found -eq 0 ]]; then
|
|
202
|
+
echo "FAIL: $file"
|
|
203
|
+
found=1
|
|
204
|
+
fi
|
|
205
|
+
echo " line $line_num: base64 blob decodes to suspicious content"
|
|
206
|
+
echo " blob: ${blob:0:60}..."
|
|
207
|
+
echo " decoded: ${decoded:0:120}"
|
|
208
|
+
echo " matched: $pattern"
|
|
209
|
+
break
|
|
210
|
+
fi
|
|
211
|
+
done
|
|
212
|
+
done <<< "$blobs"
|
|
213
|
+
done < "$file"
|
|
214
|
+
|
|
215
|
+
return $found
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
main() {
|
|
221
|
+
if [[ $# -eq 0 ]]; then
|
|
222
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path>" >&2
|
|
223
|
+
exit 2
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
load_ignorelist
|
|
227
|
+
|
|
228
|
+
local mode="$1"
|
|
229
|
+
shift
|
|
230
|
+
|
|
231
|
+
local files
|
|
232
|
+
files=$(collect_files "$mode" "$@")
|
|
233
|
+
|
|
234
|
+
if [[ -z "$files" ]]; then
|
|
235
|
+
echo "base64-scan: no files to scan"
|
|
236
|
+
exit 0
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
local total=0
|
|
240
|
+
local failed=0
|
|
241
|
+
|
|
242
|
+
while IFS= read -r file; do
|
|
243
|
+
[[ -z "$file" ]] && continue
|
|
244
|
+
if should_skip_file "$file"; then
|
|
245
|
+
continue
|
|
246
|
+
fi
|
|
247
|
+
total=$((total + 1))
|
|
248
|
+
if ! extract_and_check_blobs "$file"; then
|
|
249
|
+
failed=$((failed + 1))
|
|
250
|
+
fi
|
|
251
|
+
done <<< "$files"
|
|
252
|
+
|
|
253
|
+
echo ""
|
|
254
|
+
echo "base64-scan: scanned $total files, $failed with findings"
|
|
255
|
+
|
|
256
|
+
if [[ $failed -gt 0 ]]; then
|
|
257
|
+
exit 1
|
|
258
|
+
fi
|
|
259
|
+
exit 0
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
main "$@"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Copy GSD hooks to dist for installation.
|
|
4
|
+
* Validates JavaScript syntax before copying to prevent shipping broken hooks.
|
|
5
|
+
* See #1107, #1109, #1125, #1161 — a duplicate const declaration shipped
|
|
6
|
+
* in dist and caused PostToolUse hook errors for all users.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const vm = require('vm');
|
|
12
|
+
|
|
13
|
+
const HOOKS_DIR = path.join(__dirname, '..', 'hooks');
|
|
14
|
+
const DIST_DIR = path.join(HOOKS_DIR, 'dist');
|
|
15
|
+
|
|
16
|
+
// Hooks to copy (pure Node.js, no bundling needed)
|
|
17
|
+
const HOOKS_TO_COPY = [
|
|
18
|
+
'gsd-check-update.js',
|
|
19
|
+
'gsd-context-monitor.js',
|
|
20
|
+
'gsd-prompt-guard.js',
|
|
21
|
+
'gsd-statusline.js',
|
|
22
|
+
'gsd-workflow-guard.js'
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate JavaScript syntax without executing the file.
|
|
27
|
+
* Catches SyntaxError (duplicate const, missing brackets, etc.)
|
|
28
|
+
* before the hook gets shipped to users.
|
|
29
|
+
*/
|
|
30
|
+
function validateSyntax(filePath) {
|
|
31
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
32
|
+
try {
|
|
33
|
+
// Use vm.compileFunction to check syntax without executing
|
|
34
|
+
new vm.Script(content, { filename: path.basename(filePath) });
|
|
35
|
+
return null; // No error
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (e instanceof SyntaxError) {
|
|
38
|
+
return e.message;
|
|
39
|
+
}
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function build() {
|
|
45
|
+
// Ensure dist directory exists
|
|
46
|
+
if (!fs.existsSync(DIST_DIR)) {
|
|
47
|
+
fs.mkdirSync(DIST_DIR, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let hasErrors = false;
|
|
51
|
+
|
|
52
|
+
// Copy hooks to dist with syntax validation
|
|
53
|
+
for (const hook of HOOKS_TO_COPY) {
|
|
54
|
+
const src = path.join(HOOKS_DIR, hook);
|
|
55
|
+
const dest = path.join(DIST_DIR, hook);
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(src)) {
|
|
58
|
+
console.warn(`Warning: ${hook} not found, skipping`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate syntax before copying
|
|
63
|
+
const syntaxError = validateSyntax(src);
|
|
64
|
+
if (syntaxError) {
|
|
65
|
+
console.error(`\x1b[31m✗ ${hook}: SyntaxError — ${syntaxError}\x1b[0m`);
|
|
66
|
+
hasErrors = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`\x1b[32m✓\x1b[0m Copying ${hook}...`);
|
|
71
|
+
fs.copyFileSync(src, dest);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (hasErrors) {
|
|
75
|
+
console.error('\n\x1b[31mBuild failed: fix syntax errors above before publishing.\x1b[0m');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log('\nBuild complete.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
build();
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# prompt-injection-scan.sh — Scan files for prompt injection patterns
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# scripts/prompt-injection-scan.sh --diff origin/main # CI mode: scan changed .md files
|
|
6
|
+
# scripts/prompt-injection-scan.sh --file path/to/file # Scan a single file
|
|
7
|
+
# scripts/prompt-injection-scan.sh --dir agents/ # Scan all files in a directory
|
|
8
|
+
#
|
|
9
|
+
# Exit codes:
|
|
10
|
+
# 0 = clean
|
|
11
|
+
# 1 = findings detected
|
|
12
|
+
# 2 = usage error
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# ─── Patterns ────────────────────────────────────────────────────────────────
|
|
16
|
+
# Each pattern is a POSIX extended regex. Keep alphabetized by category.
|
|
17
|
+
|
|
18
|
+
PATTERNS=(
|
|
19
|
+
# Instruction override
|
|
20
|
+
'ignore[[:space:]]+(all[[:space:]]+)?(previous|prior|above|earlier|preceding)[[:space:]]+(instructions|prompts|rules|directives|context)'
|
|
21
|
+
'disregard[[:space:]]+(all[[:space:]]+)?(previous|prior|above)[[:space:]]+(instructions|prompts|rules)'
|
|
22
|
+
'forget[[:space:]]+(all[[:space:]]+)?(previous|prior|above)[[:space:]]+(instructions|prompts|rules|context)'
|
|
23
|
+
'override[[:space:]]+(all[[:space:]]+)?(system|previous|safety)[[:space:]]+(instructions|prompts|rules|checks|filters|guards)'
|
|
24
|
+
'override[[:space:]]+(system|safety|security)[[:space:]]'
|
|
25
|
+
|
|
26
|
+
# Role manipulation
|
|
27
|
+
'you[[:space:]]+are[[:space:]]+now[[:space:]]+(a|an|my)[[:space:]]'
|
|
28
|
+
'from[[:space:]]+now[[:space:]]+on[[:space:]]+(you|pretend|act|behave)'
|
|
29
|
+
'pretend[[:space:]]+(you[[:space:]]+are|to[[:space:]]+be)[[:space:]]'
|
|
30
|
+
'act[[:space:]]+as[[:space:]]+(a|an|if|my)[[:space:]]'
|
|
31
|
+
'roleplay[[:space:]]+as[[:space:]]'
|
|
32
|
+
'assume[[:space:]]+the[[:space:]]+role[[:space:]]+of[[:space:]]'
|
|
33
|
+
|
|
34
|
+
# System prompt extraction
|
|
35
|
+
'output[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
|
|
36
|
+
'reveal[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
|
|
37
|
+
'show[[:space:]]+me[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
|
|
38
|
+
'print[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
|
|
39
|
+
'what[[:space:]]+(is|are)[[:space:]]+(your|the)[[:space:]]+(system[[:space:]]+)?(prompt|instructions)'
|
|
40
|
+
'repeat[[:space:]]+(your|the|all)[[:space:]]+(system[[:space:]]+)?(prompt|instructions|rules)'
|
|
41
|
+
|
|
42
|
+
# Fake message boundaries
|
|
43
|
+
'</?system>'
|
|
44
|
+
'</?assistant>'
|
|
45
|
+
'</?human>'
|
|
46
|
+
'\[SYSTEM\]'
|
|
47
|
+
'\[/SYSTEM\]'
|
|
48
|
+
'\[INST\]'
|
|
49
|
+
'\[/INST\]'
|
|
50
|
+
'<<SYS>>'
|
|
51
|
+
'<</SYS>>'
|
|
52
|
+
|
|
53
|
+
# Tool call injection / code execution in markdown
|
|
54
|
+
'eval[[:space:]]*\([[:space:]]*["\x27]'
|
|
55
|
+
'exec[[:space:]]*\([[:space:]]*["\x27]'
|
|
56
|
+
'Function[[:space:]]*\([[:space:]]*["\x27].*return'
|
|
57
|
+
|
|
58
|
+
# Jailbreak / DAN patterns
|
|
59
|
+
'do[[:space:]]+anything[[:space:]]+now'
|
|
60
|
+
'DAN[[:space:]]+mode'
|
|
61
|
+
'developer[[:space:]]+mode[[:space:]]+(enabled|output|activated)'
|
|
62
|
+
'jailbreak'
|
|
63
|
+
'bypass[[:space:]]+(safety|content|security)[[:space:]]+(filter|check|rule|guard)'
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# ─── Allowlist ───────────────────────────────────────────────────────────────
|
|
67
|
+
# Files that legitimately discuss injection patterns (security docs, tests, this script)
|
|
68
|
+
ALLOWLIST=(
|
|
69
|
+
'scripts/prompt-injection-scan.sh'
|
|
70
|
+
'scripts/base64-scan.sh'
|
|
71
|
+
'scripts/secret-scan.sh'
|
|
72
|
+
'tests/security-scan.test.cjs'
|
|
73
|
+
'tests/security.test.cjs'
|
|
74
|
+
'tests/prompt-injection-scan.test.cjs'
|
|
75
|
+
'get-shit-done/bin/lib/security.cjs'
|
|
76
|
+
'hooks/gsd-prompt-guard.js'
|
|
77
|
+
'SECURITY.md'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
is_allowlisted() {
|
|
81
|
+
local file="$1"
|
|
82
|
+
for allowed in "${ALLOWLIST[@]}"; do
|
|
83
|
+
if [[ "$file" == *"$allowed" ]]; then
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
return 1
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# ─── File Collection ─────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
collect_files() {
|
|
93
|
+
local mode="$1"
|
|
94
|
+
shift
|
|
95
|
+
|
|
96
|
+
case "$mode" in
|
|
97
|
+
--diff)
|
|
98
|
+
local base="${1:-origin/main}"
|
|
99
|
+
# Get changed files in the diff, filter to scannable extensions
|
|
100
|
+
git diff --name-only --diff-filter=ACMR "$base"...HEAD 2>/dev/null \
|
|
101
|
+
| grep -E '\.(md|cjs|js|json|yml|yaml|sh)$' || true
|
|
102
|
+
;;
|
|
103
|
+
--file)
|
|
104
|
+
if [[ -f "$1" ]]; then
|
|
105
|
+
echo "$1"
|
|
106
|
+
else
|
|
107
|
+
echo "Error: file not found: $1" >&2
|
|
108
|
+
exit 2
|
|
109
|
+
fi
|
|
110
|
+
;;
|
|
111
|
+
--dir)
|
|
112
|
+
local dir="$1"
|
|
113
|
+
if [[ ! -d "$dir" ]]; then
|
|
114
|
+
echo "Error: directory not found: $dir" >&2
|
|
115
|
+
exit 2
|
|
116
|
+
fi
|
|
117
|
+
find "$dir" -type f \( -name '*.md' -o -name '*.cjs' -o -name '*.js' -o -name '*.json' -o -name '*.yml' -o -name '*.yaml' -o -name '*.sh' \) \
|
|
118
|
+
! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/dist/*' 2>/dev/null || true
|
|
119
|
+
;;
|
|
120
|
+
--stdin)
|
|
121
|
+
cat
|
|
122
|
+
;;
|
|
123
|
+
*)
|
|
124
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path> | --stdin" >&2
|
|
125
|
+
exit 2
|
|
126
|
+
;;
|
|
127
|
+
esac
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# ─── Scanner ─────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
scan_file() {
|
|
133
|
+
local file="$1"
|
|
134
|
+
local found=0
|
|
135
|
+
|
|
136
|
+
if is_allowlisted "$file"; then
|
|
137
|
+
return 0
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
for pattern in "${PATTERNS[@]}"; do
|
|
141
|
+
# Use grep -iE for case-insensitive extended regex
|
|
142
|
+
# -n for line numbers, -c for count mode first to check
|
|
143
|
+
local matches
|
|
144
|
+
matches=$(grep -inE -e "$pattern" "$file" 2>/dev/null || true)
|
|
145
|
+
if [[ -n "$matches" ]]; then
|
|
146
|
+
if [[ $found -eq 0 ]]; then
|
|
147
|
+
echo "FAIL: $file"
|
|
148
|
+
found=1
|
|
149
|
+
fi
|
|
150
|
+
echo "$matches" | while IFS= read -r line; do
|
|
151
|
+
echo " $line"
|
|
152
|
+
done
|
|
153
|
+
fi
|
|
154
|
+
done
|
|
155
|
+
|
|
156
|
+
return $found
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
main() {
|
|
162
|
+
if [[ $# -eq 0 ]]; then
|
|
163
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path>" >&2
|
|
164
|
+
exit 2
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
local mode="$1"
|
|
168
|
+
shift
|
|
169
|
+
|
|
170
|
+
local files
|
|
171
|
+
files=$(collect_files "$mode" "$@")
|
|
172
|
+
|
|
173
|
+
if [[ -z "$files" ]]; then
|
|
174
|
+
echo "prompt-injection-scan: no files to scan"
|
|
175
|
+
exit 0
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
local total=0
|
|
179
|
+
local failed=0
|
|
180
|
+
|
|
181
|
+
while IFS= read -r file; do
|
|
182
|
+
[[ -z "$file" ]] && continue
|
|
183
|
+
total=$((total + 1))
|
|
184
|
+
if ! scan_file "$file"; then
|
|
185
|
+
failed=$((failed + 1))
|
|
186
|
+
fi
|
|
187
|
+
done <<< "$files"
|
|
188
|
+
|
|
189
|
+
echo ""
|
|
190
|
+
echo "prompt-injection-scan: scanned $total files, $failed with findings"
|
|
191
|
+
|
|
192
|
+
if [[ $failed -gt 0 ]]; then
|
|
193
|
+
exit 1
|
|
194
|
+
fi
|
|
195
|
+
exit 0
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
main "$@"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Cross-platform test runner — resolves test file globs via Node
|
|
3
|
+
// instead of relying on shell expansion (which fails on Windows PowerShell/cmd).
|
|
4
|
+
// Propagates NODE_V8_COVERAGE so c8 collects coverage from the child process.
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
const { readdirSync } = require('fs');
|
|
8
|
+
const { join } = require('path');
|
|
9
|
+
const { execFileSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const testDir = join(__dirname, '..', 'tests');
|
|
12
|
+
const files = readdirSync(testDir)
|
|
13
|
+
.filter(f => f.endsWith('.test.cjs'))
|
|
14
|
+
.sort()
|
|
15
|
+
.map(f => join('tests', f));
|
|
16
|
+
|
|
17
|
+
if (files.length === 0) {
|
|
18
|
+
console.error('No test files found in tests/');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
execFileSync(process.execPath, ['--test', ...files], {
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
env: { ...process.env },
|
|
26
|
+
});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
process.exit(err.status || 1);
|
|
29
|
+
}
|