cap-pro 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/.claude-plugin/README.md +26 -0
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +806 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/cap-architect.md +269 -0
- package/agents/cap-brainstormer.md +207 -0
- package/agents/cap-curator.md +276 -0
- package/agents/cap-debugger.md +365 -0
- package/agents/cap-designer.md +246 -0
- package/agents/cap-historian.md +464 -0
- package/agents/cap-migrator.md +291 -0
- package/agents/cap-prototyper.md +197 -0
- package/agents/cap-validator.md +308 -0
- package/bin/install.js +5433 -0
- package/cap/bin/cap-tools.cjs +853 -0
- package/cap/bin/lib/arc-scanner.cjs +344 -0
- package/cap/bin/lib/cap-affinity-engine.cjs +862 -0
- package/cap/bin/lib/cap-anchor.cjs +228 -0
- package/cap/bin/lib/cap-annotation-writer.cjs +340 -0
- package/cap/bin/lib/cap-checkpoint.cjs +434 -0
- package/cap/bin/lib/cap-cluster-detect.cjs +945 -0
- package/cap/bin/lib/cap-cluster-display.cjs +52 -0
- package/cap/bin/lib/cap-cluster-format.cjs +245 -0
- package/cap/bin/lib/cap-cluster-helpers.cjs +295 -0
- package/cap/bin/lib/cap-cluster-io.cjs +212 -0
- package/cap/bin/lib/cap-completeness.cjs +540 -0
- package/cap/bin/lib/cap-deps.cjs +583 -0
- package/cap/bin/lib/cap-design-families.cjs +332 -0
- package/cap/bin/lib/cap-design.cjs +966 -0
- package/cap/bin/lib/cap-divergence-detector.cjs +400 -0
- package/cap/bin/lib/cap-doctor.cjs +752 -0
- package/cap/bin/lib/cap-feature-map-internals.cjs +19 -0
- package/cap/bin/lib/cap-feature-map-migrate.cjs +335 -0
- package/cap/bin/lib/cap-feature-map-monorepo.cjs +885 -0
- package/cap/bin/lib/cap-feature-map-shard.cjs +315 -0
- package/cap/bin/lib/cap-feature-map.cjs +1943 -0
- package/cap/bin/lib/cap-fitness-score.cjs +1075 -0
- package/cap/bin/lib/cap-impact-analysis.cjs +652 -0
- package/cap/bin/lib/cap-learn-review.cjs +1072 -0
- package/cap/bin/lib/cap-learning-signals.cjs +627 -0
- package/cap/bin/lib/cap-loader.cjs +227 -0
- package/cap/bin/lib/cap-logger.cjs +57 -0
- package/cap/bin/lib/cap-memory-bridge.cjs +764 -0
- package/cap/bin/lib/cap-memory-confidence.cjs +452 -0
- package/cap/bin/lib/cap-memory-dir.cjs +987 -0
- package/cap/bin/lib/cap-memory-engine.cjs +698 -0
- package/cap/bin/lib/cap-memory-extends.cjs +398 -0
- package/cap/bin/lib/cap-memory-graph.cjs +790 -0
- package/cap/bin/lib/cap-memory-migrate.cjs +2015 -0
- package/cap/bin/lib/cap-memory-pin.cjs +183 -0
- package/cap/bin/lib/cap-memory-platform.cjs +490 -0
- package/cap/bin/lib/cap-memory-prune.cjs +707 -0
- package/cap/bin/lib/cap-memory-schema.cjs +812 -0
- package/cap/bin/lib/cap-migrate-tags.cjs +309 -0
- package/cap/bin/lib/cap-migrate.cjs +540 -0
- package/cap/bin/lib/cap-pattern-apply.cjs +1203 -0
- package/cap/bin/lib/cap-pattern-pipeline.cjs +1034 -0
- package/cap/bin/lib/cap-plugin-manifest.cjs +80 -0
- package/cap/bin/lib/cap-realtime-affinity.cjs +399 -0
- package/cap/bin/lib/cap-reconcile.cjs +570 -0
- package/cap/bin/lib/cap-research-gate.cjs +218 -0
- package/cap/bin/lib/cap-scope-filter.cjs +402 -0
- package/cap/bin/lib/cap-semantic-pipeline.cjs +1038 -0
- package/cap/bin/lib/cap-session-extract.cjs +987 -0
- package/cap/bin/lib/cap-session.cjs +445 -0
- package/cap/bin/lib/cap-snapshot-linkage.cjs +963 -0
- package/cap/bin/lib/cap-stack-docs.cjs +646 -0
- package/cap/bin/lib/cap-tag-observer.cjs +371 -0
- package/cap/bin/lib/cap-tag-scanner.cjs +1766 -0
- package/cap/bin/lib/cap-telemetry.cjs +466 -0
- package/cap/bin/lib/cap-test-audit.cjs +1438 -0
- package/cap/bin/lib/cap-thread-migrator.cjs +307 -0
- package/cap/bin/lib/cap-thread-synthesis.cjs +545 -0
- package/cap/bin/lib/cap-thread-tracker.cjs +519 -0
- package/cap/bin/lib/cap-trace.cjs +399 -0
- package/cap/bin/lib/cap-trust-mode.cjs +336 -0
- package/cap/bin/lib/cap-ui-design-editor.cjs +642 -0
- package/cap/bin/lib/cap-ui-mind-map.cjs +712 -0
- package/cap/bin/lib/cap-ui-thread-nav.cjs +693 -0
- package/cap/bin/lib/cap-ui.cjs +1245 -0
- package/cap/bin/lib/cap-upgrade.cjs +1028 -0
- package/cap/bin/lib/cli/arg-helpers.cjs +49 -0
- package/cap/bin/lib/cli/frontmatter-router.cjs +31 -0
- package/cap/bin/lib/cli/init-router.cjs +68 -0
- package/cap/bin/lib/cli/phase-router.cjs +102 -0
- package/cap/bin/lib/cli/state-router.cjs +61 -0
- package/cap/bin/lib/cli/template-router.cjs +37 -0
- package/cap/bin/lib/cli/uat-router.cjs +29 -0
- package/cap/bin/lib/cli/validation-router.cjs +26 -0
- package/cap/bin/lib/cli/verification-router.cjs +31 -0
- package/cap/bin/lib/cli/workstream-router.cjs +39 -0
- package/cap/bin/lib/commands.cjs +961 -0
- package/cap/bin/lib/config.cjs +467 -0
- package/cap/bin/lib/convention-reader.cjs +258 -0
- package/cap/bin/lib/core.cjs +1241 -0
- package/cap/bin/lib/feature-aggregator.cjs +423 -0
- package/cap/bin/lib/frontmatter.cjs +337 -0
- package/cap/bin/lib/init.cjs +1443 -0
- package/cap/bin/lib/manifest-generator.cjs +383 -0
- package/cap/bin/lib/milestone.cjs +253 -0
- package/cap/bin/lib/model-profiles.cjs +69 -0
- package/cap/bin/lib/monorepo-context.cjs +226 -0
- package/cap/bin/lib/monorepo-migrator.cjs +509 -0
- package/cap/bin/lib/phase.cjs +889 -0
- package/cap/bin/lib/profile-output.cjs +989 -0
- package/cap/bin/lib/profile-pipeline.cjs +540 -0
- package/cap/bin/lib/roadmap.cjs +330 -0
- package/cap/bin/lib/security.cjs +394 -0
- package/cap/bin/lib/session-manager.cjs +292 -0
- package/cap/bin/lib/skeleton-generator.cjs +179 -0
- package/cap/bin/lib/state.cjs +1032 -0
- package/cap/bin/lib/template.cjs +231 -0
- package/cap/bin/lib/test-detector.cjs +62 -0
- package/cap/bin/lib/uat.cjs +283 -0
- package/cap/bin/lib/verify.cjs +889 -0
- package/cap/bin/lib/workspace-detector.cjs +371 -0
- package/cap/bin/lib/workstream.cjs +492 -0
- package/cap/commands/gsd/workstreams.md +63 -0
- package/cap/references/arc-standard.md +315 -0
- package/cap/references/cap-agent-architecture.md +101 -0
- package/cap/references/cap-gitignore-template +9 -0
- package/cap/references/cap-zero-deps.md +158 -0
- package/cap/references/checkpoints.md +778 -0
- package/cap/references/continuation-format.md +249 -0
- package/cap/references/contract-test-templates.md +312 -0
- package/cap/references/feature-map-template.md +25 -0
- package/cap/references/git-integration.md +295 -0
- package/cap/references/git-planning-commit.md +38 -0
- package/cap/references/model-profiles.md +174 -0
- package/cap/references/phase-numbering.md +126 -0
- package/cap/references/planning-config.md +202 -0
- package/cap/references/property-test-templates.md +316 -0
- package/cap/references/security-test-templates.md +347 -0
- package/cap/references/session-template.json +8 -0
- package/cap/references/tdd.md +263 -0
- package/cap/references/user-profiling.md +681 -0
- package/cap/references/verification-patterns.md +612 -0
- package/cap/templates/UAT.md +265 -0
- package/cap/templates/claude-md.md +175 -0
- package/cap/templates/codebase/architecture.md +255 -0
- package/cap/templates/codebase/concerns.md +310 -0
- package/cap/templates/codebase/conventions.md +307 -0
- package/cap/templates/codebase/integrations.md +280 -0
- package/cap/templates/codebase/stack.md +186 -0
- package/cap/templates/codebase/structure.md +285 -0
- package/cap/templates/codebase/testing.md +480 -0
- package/cap/templates/config.json +44 -0
- package/cap/templates/context.md +352 -0
- package/cap/templates/continue-here.md +78 -0
- package/cap/templates/copilot-instructions.md +7 -0
- package/cap/templates/debug-subagent-prompt.md +91 -0
- package/cap/templates/discussion-log.md +63 -0
- package/cap/templates/milestone-archive.md +123 -0
- package/cap/templates/milestone.md +115 -0
- package/cap/templates/phase-prompt.md +610 -0
- package/cap/templates/planner-subagent-prompt.md +117 -0
- package/cap/templates/project.md +186 -0
- package/cap/templates/requirements.md +231 -0
- package/cap/templates/research-project/ARCHITECTURE.md +204 -0
- package/cap/templates/research-project/FEATURES.md +147 -0
- package/cap/templates/research-project/PITFALLS.md +200 -0
- package/cap/templates/research-project/STACK.md +120 -0
- package/cap/templates/research-project/SUMMARY.md +170 -0
- package/cap/templates/research.md +552 -0
- package/cap/templates/roadmap.md +202 -0
- package/cap/templates/state.md +176 -0
- package/cap/templates/summary.md +364 -0
- package/cap/templates/user-preferences.md +498 -0
- package/cap/templates/verification-report.md +322 -0
- package/cap/workflows/add-phase.md +112 -0
- package/cap/workflows/add-tests.md +351 -0
- package/cap/workflows/add-todo.md +158 -0
- package/cap/workflows/audit-milestone.md +340 -0
- package/cap/workflows/audit-uat.md +109 -0
- package/cap/workflows/autonomous.md +891 -0
- package/cap/workflows/check-todos.md +177 -0
- package/cap/workflows/cleanup.md +152 -0
- package/cap/workflows/complete-milestone.md +767 -0
- package/cap/workflows/diagnose-issues.md +231 -0
- package/cap/workflows/discovery-phase.md +289 -0
- package/cap/workflows/discuss-phase-assumptions.md +653 -0
- package/cap/workflows/discuss-phase.md +1049 -0
- package/cap/workflows/do.md +104 -0
- package/cap/workflows/execute-phase.md +846 -0
- package/cap/workflows/execute-plan.md +514 -0
- package/cap/workflows/fast.md +105 -0
- package/cap/workflows/forensics.md +265 -0
- package/cap/workflows/health.md +181 -0
- package/cap/workflows/help.md +660 -0
- package/cap/workflows/insert-phase.md +130 -0
- package/cap/workflows/list-phase-assumptions.md +178 -0
- package/cap/workflows/list-workspaces.md +56 -0
- package/cap/workflows/manager.md +362 -0
- package/cap/workflows/map-codebase.md +377 -0
- package/cap/workflows/milestone-summary.md +223 -0
- package/cap/workflows/new-milestone.md +486 -0
- package/cap/workflows/new-project.md +1250 -0
- package/cap/workflows/new-workspace.md +237 -0
- package/cap/workflows/next.md +97 -0
- package/cap/workflows/node-repair.md +92 -0
- package/cap/workflows/note.md +156 -0
- package/cap/workflows/pause-work.md +176 -0
- package/cap/workflows/plan-milestone-gaps.md +273 -0
- package/cap/workflows/plan-phase.md +857 -0
- package/cap/workflows/plant-seed.md +169 -0
- package/cap/workflows/pr-branch.md +129 -0
- package/cap/workflows/profile-user.md +449 -0
- package/cap/workflows/progress.md +507 -0
- package/cap/workflows/quick.md +757 -0
- package/cap/workflows/remove-phase.md +155 -0
- package/cap/workflows/remove-workspace.md +90 -0
- package/cap/workflows/research-phase.md +82 -0
- package/cap/workflows/resume-project.md +326 -0
- package/cap/workflows/review.md +228 -0
- package/cap/workflows/session-report.md +146 -0
- package/cap/workflows/settings.md +283 -0
- package/cap/workflows/ship.md +228 -0
- package/cap/workflows/stats.md +60 -0
- package/cap/workflows/transition.md +671 -0
- package/cap/workflows/ui-phase.md +298 -0
- package/cap/workflows/ui-review.md +161 -0
- package/cap/workflows/update.md +323 -0
- package/cap/workflows/validate-phase.md +170 -0
- package/cap/workflows/verify-phase.md +254 -0
- package/cap/workflows/verify-work.md +637 -0
- package/commands/cap/annotate.md +165 -0
- package/commands/cap/brainstorm.md +393 -0
- package/commands/cap/checkpoint.md +106 -0
- package/commands/cap/completeness.md +94 -0
- package/commands/cap/continue.md +72 -0
- package/commands/cap/debug.md +588 -0
- package/commands/cap/deps.md +169 -0
- package/commands/cap/design.md +479 -0
- package/commands/cap/init.md +354 -0
- package/commands/cap/iterate.md +249 -0
- package/commands/cap/learn.md +459 -0
- package/commands/cap/memory.md +275 -0
- package/commands/cap/migrate-feature-map.md +91 -0
- package/commands/cap/migrate-memory.md +108 -0
- package/commands/cap/migrate-tags.md +91 -0
- package/commands/cap/migrate.md +131 -0
- package/commands/cap/prototype.md +510 -0
- package/commands/cap/reconcile.md +121 -0
- package/commands/cap/review.md +360 -0
- package/commands/cap/save.md +72 -0
- package/commands/cap/scan.md +404 -0
- package/commands/cap/start.md +356 -0
- package/commands/cap/status.md +118 -0
- package/commands/cap/test-audit.md +262 -0
- package/commands/cap/test.md +394 -0
- package/commands/cap/trace.md +133 -0
- package/commands/cap/ui.md +167 -0
- package/hooks/dist/cap-check-update.js +115 -0
- package/hooks/dist/cap-context-monitor.js +185 -0
- package/hooks/dist/cap-learn-review-hook.js +114 -0
- package/hooks/dist/cap-learning-hook.js +192 -0
- package/hooks/dist/cap-memory.js +299 -0
- package/hooks/dist/cap-prompt-guard.js +97 -0
- package/hooks/dist/cap-statusline.js +157 -0
- package/hooks/dist/cap-tag-observer.js +115 -0
- package/hooks/dist/cap-version-check.js +112 -0
- package/hooks/dist/cap-workflow-guard.js +175 -0
- package/hooks/hooks.json +55 -0
- package/package.json +58 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +93 -0
- package/scripts/cap-removal-checklist.md +202 -0
- package/scripts/prompt-injection-scan.sh +199 -0
- package/scripts/run-tests.cjs +181 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
'tests/cap-prompt-guard.test.cjs'
|
|
76
|
+
'get-shit-done/bin/lib/security.cjs'
|
|
77
|
+
'hooks/gsd-prompt-guard.js'
|
|
78
|
+
'SECURITY.md'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
is_allowlisted() {
|
|
82
|
+
local file="$1"
|
|
83
|
+
for allowed in "${ALLOWLIST[@]}"; do
|
|
84
|
+
if [[ "$file" == *"$allowed" ]]; then
|
|
85
|
+
return 0
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
return 1
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# ─── File Collection ─────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
collect_files() {
|
|
94
|
+
local mode="$1"
|
|
95
|
+
shift
|
|
96
|
+
|
|
97
|
+
case "$mode" in
|
|
98
|
+
--diff)
|
|
99
|
+
local base="${1:-origin/main}"
|
|
100
|
+
# Get changed files in the diff, filter to scannable extensions
|
|
101
|
+
git diff --name-only --diff-filter=ACMR "$base"...HEAD 2>/dev/null \
|
|
102
|
+
| grep -E '\.(md|cjs|js|json|yml|yaml|sh)$' || true
|
|
103
|
+
;;
|
|
104
|
+
--file)
|
|
105
|
+
if [[ -f "$1" ]]; then
|
|
106
|
+
echo "$1"
|
|
107
|
+
else
|
|
108
|
+
echo "Error: file not found: $1" >&2
|
|
109
|
+
exit 2
|
|
110
|
+
fi
|
|
111
|
+
;;
|
|
112
|
+
--dir)
|
|
113
|
+
local dir="$1"
|
|
114
|
+
if [[ ! -d "$dir" ]]; then
|
|
115
|
+
echo "Error: directory not found: $dir" >&2
|
|
116
|
+
exit 2
|
|
117
|
+
fi
|
|
118
|
+
find "$dir" -type f \( -name '*.md' -o -name '*.cjs' -o -name '*.js' -o -name '*.json' -o -name '*.yml' -o -name '*.yaml' -o -name '*.sh' \) \
|
|
119
|
+
! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/dist/*' 2>/dev/null || true
|
|
120
|
+
;;
|
|
121
|
+
--stdin)
|
|
122
|
+
cat
|
|
123
|
+
;;
|
|
124
|
+
*)
|
|
125
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path> | --stdin" >&2
|
|
126
|
+
exit 2
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# ─── Scanner ─────────────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
scan_file() {
|
|
134
|
+
local file="$1"
|
|
135
|
+
local found=0
|
|
136
|
+
|
|
137
|
+
if is_allowlisted "$file"; then
|
|
138
|
+
return 0
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
for pattern in "${PATTERNS[@]}"; do
|
|
142
|
+
# Use grep -iE for case-insensitive extended regex
|
|
143
|
+
# -n for line numbers, -c for count mode first to check
|
|
144
|
+
local matches
|
|
145
|
+
matches=$(grep -inE -e "$pattern" "$file" 2>/dev/null || true)
|
|
146
|
+
if [[ -n "$matches" ]]; then
|
|
147
|
+
if [[ $found -eq 0 ]]; then
|
|
148
|
+
echo "FAIL: $file"
|
|
149
|
+
found=1
|
|
150
|
+
fi
|
|
151
|
+
echo "$matches" | while IFS= read -r line; do
|
|
152
|
+
echo " $line"
|
|
153
|
+
done
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
|
|
157
|
+
return $found
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
main() {
|
|
163
|
+
if [[ $# -eq 0 ]]; then
|
|
164
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path>" >&2
|
|
165
|
+
exit 2
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
local mode="$1"
|
|
169
|
+
shift
|
|
170
|
+
|
|
171
|
+
local files
|
|
172
|
+
files=$(collect_files "$mode" "$@")
|
|
173
|
+
|
|
174
|
+
if [[ -z "$files" ]]; then
|
|
175
|
+
echo "prompt-injection-scan: no files to scan"
|
|
176
|
+
exit 0
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
local total=0
|
|
180
|
+
local failed=0
|
|
181
|
+
|
|
182
|
+
while IFS= read -r file; do
|
|
183
|
+
[[ -z "$file" ]] && continue
|
|
184
|
+
total=$((total + 1))
|
|
185
|
+
if ! scan_file "$file"; then
|
|
186
|
+
failed=$((failed + 1))
|
|
187
|
+
fi
|
|
188
|
+
done <<< "$files"
|
|
189
|
+
|
|
190
|
+
echo ""
|
|
191
|
+
echo "prompt-injection-scan: scanned $total files, $failed with findings"
|
|
192
|
+
|
|
193
|
+
if [[ $failed -gt 0 ]]; then
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|
|
196
|
+
exit 0
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
main "$@"
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @cap-history(sessions:3, edits:12, since:2026-04-20, learned:2026-05-06) Frequently modified — 3 sessions, 12 edits
|
|
3
|
+
// Cross-platform test runner — resolves test file globs via Node
|
|
4
|
+
// instead of relying on shell expansion (which fails on Windows PowerShell/cmd).
|
|
5
|
+
// Pass --coverage to enable Node's native experimental test coverage
|
|
6
|
+
// (c8 misses data from `node --test` isolation subprocesses on Node 22+).
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { readdirSync } = require('fs');
|
|
10
|
+
const { join } = require('path');
|
|
11
|
+
const { execFileSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
const wantsCoverage = process.argv.includes('--coverage');
|
|
14
|
+
const testDir = join(__dirname, '..', 'tests');
|
|
15
|
+
const files = readdirSync(testDir)
|
|
16
|
+
.filter(f => f.endsWith('.test.cjs'))
|
|
17
|
+
.sort()
|
|
18
|
+
.map(f => join('tests', f));
|
|
19
|
+
|
|
20
|
+
if (files.length === 0) {
|
|
21
|
+
console.error('No test files found in tests/');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The default TAP reporter swallows failure details under
|
|
26
|
+
// --experimental-test-isolation=none (only `# fail N` in the summary, no
|
|
27
|
+
// per-test `not ok` lines), which makes red CI unnecessarily opaque.
|
|
28
|
+
// The spec reporter emits explicit ✖ lines with the assertion error.
|
|
29
|
+
const nodeArgs = ['--test', '--test-reporter=spec'];
|
|
30
|
+
if (wantsCoverage) {
|
|
31
|
+
// Node >=22 defaults to process-per-file isolation. Coverage from those
|
|
32
|
+
// subprocesses is dropped by both c8 and the native reporter, so force
|
|
33
|
+
// single-process execution when measuring coverage. Plain `npm test` keeps
|
|
34
|
+
// the safer default isolation on purpose: it surfaces shared-state leaks
|
|
35
|
+
// (F-052 was found exactly this way), which isolation=none would hide.
|
|
36
|
+
// Flag-name history: `--experimental-test-isolation` landed in Node v22.8.0;
|
|
37
|
+
// the non-experimental `--test-isolation=...` form was stabilised in v23.x.
|
|
38
|
+
// CI runs on Node 22 reject the stabilised name with "bad option", so we use
|
|
39
|
+
// the experimental prefix — Node 23+ still accepts it for back-compat.
|
|
40
|
+
// Discovered during the 2026-04-21 F-054..F-059 batch: every feature PR
|
|
41
|
+
// merged with red CI because this mismatch failed the runner before a single
|
|
42
|
+
// test executed.
|
|
43
|
+
//
|
|
44
|
+
// @cap-decision(CI/issue-42) Path 1 rejected — DOUBLE-CONFIRMED.
|
|
45
|
+
//
|
|
46
|
+
// First measurement (2026-05-07, before Path 2): dropping
|
|
47
|
+
// --experimental-test-isolation=none cut wall-time 43x (1437s -> 33s) but
|
|
48
|
+
// collapsed line coverage 96.95% -> 56.31% (-40.64pp). Hypothesis: subprocess
|
|
49
|
+
// fixtures via `runGsdTools` (helpers.cjs:21) hide coverage from the native
|
|
50
|
+
// --experimental-test-coverage aggregator. Bridge fix PR #46 raised CI
|
|
51
|
+
// timeout 10->20 min. Path 2 plan: migrate fixtures to in-process module calls.
|
|
52
|
+
//
|
|
53
|
+
// Second measurement (2026-05-07, after Path 2 Phase 1-3 -- PRs #54-#61
|
|
54
|
+
// migrated 552 callsites across 14 files): re-ran the same Path 1 change.
|
|
55
|
+
// Wall-time 1298s -> 26s (49.9x faster); line coverage 97.40% -> 55.83%
|
|
56
|
+
// (-41.57pp). Per-hot-file deltas remain catastrophic (cap-feature-map.cjs
|
|
57
|
+
// -72.78, cap-feature-map-monorepo.cjs -71.19, cap-memory-migrate.cjs
|
|
58
|
+
// -70.26, cap-tag-scanner.cjs -61.23). The 552 migrations had near-zero
|
|
59
|
+
// effect on the coverage gap.
|
|
60
|
+
//
|
|
61
|
+
// Revised root-cause: NOT subprocess fixtures. Node 22's native
|
|
62
|
+
// --experimental-test-coverage aggregator does not merge coverage across
|
|
63
|
+
// test-file workers -- only the parent process is counted. Under per-file
|
|
64
|
+
// isolation each test file becomes its own worker; their coverage data is
|
|
65
|
+
// not surfaced. The gap is at the worker->parent boundary, NOT
|
|
66
|
+
// fixture->parent. Migrating runGsdTools to in-process moved work from
|
|
67
|
+
// grand-child fixtures into worker processes, which the aggregator still
|
|
68
|
+
// doesn't see.
|
|
69
|
+
//
|
|
70
|
+
// Phase 4 (migrate the remaining 248 callsites in 9 files: dispatcher,
|
|
71
|
+
// frontmatter-cli, init-manager, profile-pipeline, milestone, roadmap,
|
|
72
|
+
// template, verify, uat) WILL NOT close the gap -- those are CLI-dispatch
|
|
73
|
+
// tests, not the hot lib modules whose coverage collapses. Diagnosed
|
|
74
|
+
// empirically.
|
|
75
|
+
//
|
|
76
|
+
// Three real remediation paths considered:
|
|
77
|
+
// 1. Switch to c8/nyc (hypothesis: writes per-worker JSON, supports merge
|
|
78
|
+
// across workers). REJECTED — see @cap-decision below.
|
|
79
|
+
// 2. Upgrade CI to Node 23+ and switch to stable --test-isolation=process
|
|
80
|
+
// with v23's worker-coverage-merge fixes. ~0.5 day, needs verification.
|
|
81
|
+
// 3. Status quo: keep --experimental-test-isolation=none, ~21 min CI runs,
|
|
82
|
+
// 97.4% coverage. F-052-class race detection sacrificed by design here
|
|
83
|
+
// but covered by plain `npm test` worker isolation.
|
|
84
|
+
//
|
|
85
|
+
// @cap-decision(CI/issue-42 c8-also-rejected) Path 1 (c8 variant) REJECTED.
|
|
86
|
+
//
|
|
87
|
+
// Hypothesis from PR #62 follow-up: c8 instruments via a require()-hook at
|
|
88
|
+
// module-load time and writes per-worker JSON to coverage/tmp/, so it should
|
|
89
|
+
// fix the worker-aggregator gap that bit native --experimental-test-coverage.
|
|
90
|
+
// Empirically tested 2026-05-07 with c8 v10.1.3 + Node 24 + --test-isolation=
|
|
91
|
+
// process (the stabilised flag). Three configurations measured:
|
|
92
|
+
//
|
|
93
|
+
// A. native + --experimental-test-isolation=none .... 97.40% lines, 1240s
|
|
94
|
+
// (current production, unchanged since F-051)
|
|
95
|
+
// B. c8 + --test-isolation=process (default) .... 55.18% lines, 26.4s
|
|
96
|
+
// (the hypothesised win — REJECTED, same gap as native+Path 1's 55.83%)
|
|
97
|
+
// C. c8 + --experimental-test-isolation=none .... 97.39% lines, 1258s
|
|
98
|
+
// (parity check — confirms c8 matches native at the same isolation)
|
|
99
|
+
//
|
|
100
|
+
// Root cause of the c8 rejection: c8 v10 does NOT use a require()-hook. The
|
|
101
|
+
// hypothesis was wrong about how c8 works. c8 v10 just sets NODE_V8_COVERAGE
|
|
102
|
+
// before spawning the wrapped command and reads V8's per-process JSONs on
|
|
103
|
+
// exit — it is a thin wrapper around Node's *native* coverage mechanism.
|
|
104
|
+
// Forensic check on coverage/tmp/ after the c8+Path1 run: 308 worker JSONs
|
|
105
|
+
// were written but only 46 of 167 test files appeared in any of them. The
|
|
106
|
+
// missing 121 test files are alphabetically-early (agent-*, antigravity-*,
|
|
107
|
+
// arc-*, build-*, cap-affinity through cap-divergence, cap-feature-map.*,
|
|
108
|
+
// most cap-memory-*, cap-tag-*, etc). This is the SAME Node-level bug:
|
|
109
|
+
// --test-isolation=process pool workers spawn before NODE_V8_COVERAGE has
|
|
110
|
+
// had its on-exit handler armed, so early workers exit without writing.
|
|
111
|
+
// Switching tools cannot fix this — both tools sit downstream of the same
|
|
112
|
+
// V8/Node coverage mechanism.
|
|
113
|
+
//
|
|
114
|
+
// Phase 4 is still NOT necessary. The remaining remediation options are:
|
|
115
|
+
// - Node 23+ upgrade + stable --test-isolation=process with verified
|
|
116
|
+
// worker-coverage-merge (Remediation Path 2 above).
|
|
117
|
+
// - Persist current --experimental-test-isolation=none status quo.
|
|
118
|
+
// c8 was the original tool, removed in F-051 (commit 8cc51fd) under the same
|
|
119
|
+
// empirical evidence — that decision stands.
|
|
120
|
+
//
|
|
121
|
+
// Until Path 2 lands: the flag stays. The 552 in-process migrations from
|
|
122
|
+
// PRs #54-#61 remain net wins (faster `npm test` runs locally + cleaner test
|
|
123
|
+
// architecture) but neither they nor c8 close the coverage gap on Path 1.
|
|
124
|
+
//
|
|
125
|
+
// @cap-decision(CI/issue-42 quadruple-rejection) Path 1 also rejected on
|
|
126
|
+
// Node 22, 23, 25 — confirms the bug is structural, not version-specific.
|
|
127
|
+
//
|
|
128
|
+
// Empirical Node-version sweep (2026-05-07): tested Path 1 across all four
|
|
129
|
+
// current Node majors with both flag forms:
|
|
130
|
+
//
|
|
131
|
+
// Node 22.22.2 + Path 1 (--experimental-...=none removed) .. 55.22% lines, 23s
|
|
132
|
+
// Node 23.11.1 + Path 1 ........................................ 55.84% lines, 23s
|
|
133
|
+
// Node 24.15.0 + Path 1 ........................................ 55.11% lines, 26s
|
|
134
|
+
// Node 25.9.0 + Path 1 ........................................ 55.04% lines, 26s
|
|
135
|
+
// Node 23.11.1 + --test-isolation=process (stabilised flag) ... 55.43% lines, 26s
|
|
136
|
+
// Node 25.9.0 + --test-isolation=process (stabilised flag) ... 55.15% lines, 28s
|
|
137
|
+
//
|
|
138
|
+
// Hot-file numbers were BYTE-IDENTICAL across all 6 Path-1 runs. This is
|
|
139
|
+
// deterministic missing-aggregation, not flaky measurement. The bug is
|
|
140
|
+
// present in Node 22 through 25 and survives both flag forms.
|
|
141
|
+
//
|
|
142
|
+
// What this eliminates:
|
|
143
|
+
// - "Node 23+ has worker-coverage-merge fixes" — wrong, v23 has the bug.
|
|
144
|
+
// - "Upgrade fixes it" — wrong, v25 has the bug.
|
|
145
|
+
// - "Use the stabilised flag" — wrong, same bug under both forms.
|
|
146
|
+
//
|
|
147
|
+
// Final remediation set: Status quo only. The
|
|
148
|
+
// --experimental-test-isolation=none flag stays. ~21 min CI runs, ~97.4%
|
|
149
|
+
// coverage. F-052-class race detection sacrificed by design under coverage
|
|
150
|
+
// but covered by plain `npm test` worker-isolation. The 552 in-process
|
|
151
|
+
// migrations from PRs #54-#61 remain net wins for plain-test speed but did
|
|
152
|
+
// NOT enable Path 1.
|
|
153
|
+
//
|
|
154
|
+
// If a future investigator considers re-trying Path 1: don't. It has been
|
|
155
|
+
// rejected FOUR times with empirical data:
|
|
156
|
+
// 1. native pre-Path-2 (PR #62, ~40pp drop)
|
|
157
|
+
// 2. native post-Path-2 (PR #62, ~42pp drop, byte-identical hot files)
|
|
158
|
+
// 3. c8 v10 (PR #63, ~42pp drop, same hot files)
|
|
159
|
+
// 4. Node 22/23/24/25 sweep (this commit, ~42pp drop on every Node major)
|
|
160
|
+
//
|
|
161
|
+
// The actionable next step (NOT this commit, low urgency) is filing an
|
|
162
|
+
// upstream Node bug with a minimal repro showing alphabetically-early
|
|
163
|
+
// workers missing from the native coverage aggregator under per-file
|
|
164
|
+
// isolation. Until that lands and ships in some Node version: status quo.
|
|
165
|
+
nodeArgs.push(
|
|
166
|
+
'--experimental-test-isolation=none',
|
|
167
|
+
'--experimental-test-coverage',
|
|
168
|
+
'--test-coverage-lines=0.7',
|
|
169
|
+
'--test-coverage-include=cap/bin/lib/*.cjs'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
nodeArgs.push(...files);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
execFileSync(process.execPath, nodeArgs, {
|
|
176
|
+
stdio: 'inherit',
|
|
177
|
+
env: { ...process.env },
|
|
178
|
+
});
|
|
179
|
+
} catch (err) {
|
|
180
|
+
process.exit(err.status || 1);
|
|
181
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# secret-scan.sh — Check files for accidentally committed secrets/credentials
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# scripts/secret-scan.sh --diff origin/main # CI mode: scan changed files
|
|
6
|
+
# scripts/secret-scan.sh --file path/to/file # Scan a single file
|
|
7
|
+
# scripts/secret-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
|
+
# ─── Secret Patterns ─────────────────────────────────────────────────────────
|
|
16
|
+
# Format: "LABEL:::REGEX"
|
|
17
|
+
# Each entry is a human label paired with a POSIX extended regex.
|
|
18
|
+
|
|
19
|
+
SECRET_PATTERNS=(
|
|
20
|
+
# AWS
|
|
21
|
+
"AWS Access Key:::AKIA[0-9A-Z]{16}"
|
|
22
|
+
"AWS Secret Key:::aws_secret_access_key[[:space:]]*=[[:space:]]*[A-Za-z0-9/+=]{40}"
|
|
23
|
+
|
|
24
|
+
# OpenAI / Anthropic / AI providers
|
|
25
|
+
"OpenAI API Key:::sk-[A-Za-z0-9]{20,}"
|
|
26
|
+
"Anthropic API Key:::sk-ant-[A-Za-z0-9_-]{20,}"
|
|
27
|
+
|
|
28
|
+
# GitHub
|
|
29
|
+
"GitHub PAT:::ghp_[A-Za-z0-9]{36}"
|
|
30
|
+
"GitHub OAuth:::gho_[A-Za-z0-9]{36}"
|
|
31
|
+
"GitHub App Token:::ghs_[A-Za-z0-9]{36}"
|
|
32
|
+
"GitHub Fine-grained PAT:::github_pat_[A-Za-z0-9_]{20,}"
|
|
33
|
+
|
|
34
|
+
# Stripe
|
|
35
|
+
"Stripe Secret Key:::sk_live_[A-Za-z0-9]{24,}"
|
|
36
|
+
"Stripe Publishable Key:::pk_live_[A-Za-z0-9]{24,}"
|
|
37
|
+
|
|
38
|
+
# Generic patterns
|
|
39
|
+
"Private Key Header:::-----BEGIN[[:space:]]+(RSA|EC|DSA|OPENSSH)?[[:space:]]*PRIVATE[[:space:]]+KEY-----"
|
|
40
|
+
"Generic API Key Assignment:::api[_-]?key[[:space:]]*[:=][[:space:]]*['\"][A-Za-z0-9_-]{20,}['\"]"
|
|
41
|
+
"Generic Secret Assignment:::secret[[:space:]]*[:=][[:space:]]*['\"][A-Za-z0-9_-]{20,}['\"]"
|
|
42
|
+
"Generic Token Assignment:::token[[:space:]]*[:=][[:space:]]*['\"][A-Za-z0-9_-]{20,}['\"]"
|
|
43
|
+
"Generic Password Assignment:::password[[:space:]]*[:=][[:space:]]*['\"][^'\"]{8,}['\"]"
|
|
44
|
+
|
|
45
|
+
# Slack
|
|
46
|
+
"Slack Bot Token:::xoxb-[0-9]{10,}-[A-Za-z0-9]{20,}"
|
|
47
|
+
"Slack Webhook:::hooks\.slack\.com/services/T[A-Z0-9]{8,}/B[A-Z0-9]{8,}/[A-Za-z0-9]{24}"
|
|
48
|
+
|
|
49
|
+
# Google
|
|
50
|
+
"Google API Key:::AIza[A-Za-z0-9_-]{35}"
|
|
51
|
+
|
|
52
|
+
# NPM
|
|
53
|
+
"NPM Token:::npm_[A-Za-z0-9]{36}"
|
|
54
|
+
|
|
55
|
+
# .env file content (key=value with sensitive-looking keys)
|
|
56
|
+
"Env Variable Leak:::(DATABASE_URL|DB_PASSWORD|REDIS_URL|MONGO_URI|JWT_SECRET|SESSION_SECRET|ENCRYPTION_KEY)[[:space:]]*=[[:space:]]*[^[:space:]]{8,}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# ─── Ignorelist ──────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
IGNOREFILE=".secretscanignore"
|
|
62
|
+
IGNORED_FILES=()
|
|
63
|
+
|
|
64
|
+
load_ignorelist() {
|
|
65
|
+
if [[ -f "$IGNOREFILE" ]]; then
|
|
66
|
+
while IFS= read -r line; do
|
|
67
|
+
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
68
|
+
[[ -z "${line// }" ]] && continue
|
|
69
|
+
IGNORED_FILES+=("$line")
|
|
70
|
+
done < "$IGNOREFILE"
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
is_ignored() {
|
|
75
|
+
local file="$1"
|
|
76
|
+
if [[ ${#IGNORED_FILES[@]} -eq 0 ]]; then
|
|
77
|
+
return 1
|
|
78
|
+
fi
|
|
79
|
+
for pattern in "${IGNORED_FILES[@]}"; do
|
|
80
|
+
# Support glob-style matching
|
|
81
|
+
# shellcheck disable=SC2254
|
|
82
|
+
case "$file" in
|
|
83
|
+
$pattern) return 0 ;;
|
|
84
|
+
esac
|
|
85
|
+
done
|
|
86
|
+
return 1
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# ─── Skip Rules ──────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
should_skip_file() {
|
|
92
|
+
local file="$1"
|
|
93
|
+
# Skip binary files
|
|
94
|
+
case "$file" in
|
|
95
|
+
*.png|*.jpg|*.jpeg|*.gif|*.ico|*.woff|*.woff2|*.ttf|*.eot|*.otf) return 0 ;;
|
|
96
|
+
*.zip|*.tar|*.gz|*.bz2|*.xz|*.7z) return 0 ;;
|
|
97
|
+
*.pdf|*.doc|*.docx|*.xls|*.xlsx) return 0 ;;
|
|
98
|
+
esac
|
|
99
|
+
# Skip lockfiles and node_modules
|
|
100
|
+
case "$file" in
|
|
101
|
+
*/node_modules/*) return 0 ;;
|
|
102
|
+
*/package-lock.json) return 0 ;;
|
|
103
|
+
*/yarn.lock) return 0 ;;
|
|
104
|
+
*/pnpm-lock.yaml) return 0 ;;
|
|
105
|
+
esac
|
|
106
|
+
# Skip the scan scripts themselves and test files
|
|
107
|
+
case "$file" in
|
|
108
|
+
*/secret-scan.sh) return 0 ;;
|
|
109
|
+
*/security-scan.test.cjs) return 0 ;;
|
|
110
|
+
esac
|
|
111
|
+
return 1
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# ─── File Collection ─────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
collect_files() {
|
|
117
|
+
local mode="$1"
|
|
118
|
+
shift
|
|
119
|
+
|
|
120
|
+
case "$mode" in
|
|
121
|
+
--diff)
|
|
122
|
+
local base="${1:-origin/main}"
|
|
123
|
+
git diff --name-only --diff-filter=ACMR "$base"...HEAD 2>/dev/null \
|
|
124
|
+
| grep -vE '\.(png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|otf|zip|tar|gz|pdf)$' || true
|
|
125
|
+
;;
|
|
126
|
+
--file)
|
|
127
|
+
if [[ -f "$1" ]]; then
|
|
128
|
+
echo "$1"
|
|
129
|
+
else
|
|
130
|
+
echo "Error: file not found: $1" >&2
|
|
131
|
+
exit 2
|
|
132
|
+
fi
|
|
133
|
+
;;
|
|
134
|
+
--dir)
|
|
135
|
+
local dir="$1"
|
|
136
|
+
if [[ ! -d "$dir" ]]; then
|
|
137
|
+
echo "Error: directory not found: $dir" >&2
|
|
138
|
+
exit 2
|
|
139
|
+
fi
|
|
140
|
+
find "$dir" -type f ! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/dist/*' \
|
|
141
|
+
! -name '*.png' ! -name '*.jpg' ! -name '*.gif' ! -name '*.woff*' 2>/dev/null || true
|
|
142
|
+
;;
|
|
143
|
+
--stdin)
|
|
144
|
+
cat
|
|
145
|
+
;;
|
|
146
|
+
*)
|
|
147
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path> | --stdin" >&2
|
|
148
|
+
exit 2
|
|
149
|
+
;;
|
|
150
|
+
esac
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# ─── Scanner ─────────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
scan_file() {
|
|
156
|
+
local file="$1"
|
|
157
|
+
local found=0
|
|
158
|
+
|
|
159
|
+
if is_ignored "$file"; then
|
|
160
|
+
return 0
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
for entry in "${SECRET_PATTERNS[@]}"; do
|
|
164
|
+
local label="${entry%%:::*}"
|
|
165
|
+
local pattern="${entry#*:::}"
|
|
166
|
+
|
|
167
|
+
local matches
|
|
168
|
+
matches=$(grep -nE -e "$pattern" "$file" 2>/dev/null || true)
|
|
169
|
+
if [[ -n "$matches" ]]; then
|
|
170
|
+
if [[ $found -eq 0 ]]; then
|
|
171
|
+
echo "FAIL: $file"
|
|
172
|
+
found=1
|
|
173
|
+
fi
|
|
174
|
+
echo "$matches" | while IFS= read -r line; do
|
|
175
|
+
echo " [$label] $line"
|
|
176
|
+
done
|
|
177
|
+
fi
|
|
178
|
+
done
|
|
179
|
+
|
|
180
|
+
return $found
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
main() {
|
|
186
|
+
if [[ $# -eq 0 ]]; then
|
|
187
|
+
echo "Usage: $0 --diff [base] | --file <path> | --dir <path>" >&2
|
|
188
|
+
exit 2
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
load_ignorelist
|
|
192
|
+
|
|
193
|
+
local mode="$1"
|
|
194
|
+
shift
|
|
195
|
+
|
|
196
|
+
local files
|
|
197
|
+
files=$(collect_files "$mode" "$@")
|
|
198
|
+
|
|
199
|
+
if [[ -z "$files" ]]; then
|
|
200
|
+
echo "secret-scan: no files to scan"
|
|
201
|
+
exit 0
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
local total=0
|
|
205
|
+
local failed=0
|
|
206
|
+
|
|
207
|
+
while IFS= read -r file; do
|
|
208
|
+
[[ -z "$file" ]] && continue
|
|
209
|
+
if should_skip_file "$file"; then
|
|
210
|
+
continue
|
|
211
|
+
fi
|
|
212
|
+
total=$((total + 1))
|
|
213
|
+
if ! scan_file "$file"; then
|
|
214
|
+
failed=$((failed + 1))
|
|
215
|
+
fi
|
|
216
|
+
done <<< "$files"
|
|
217
|
+
|
|
218
|
+
echo ""
|
|
219
|
+
echo "secret-scan: scanned $total files, $failed with findings"
|
|
220
|
+
|
|
221
|
+
if [[ $failed -gt 0 ]]; then
|
|
222
|
+
exit 1
|
|
223
|
+
fi
|
|
224
|
+
exit 0
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main "$@"
|