@bhargavvc/sdd-cc 1.30.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 +846 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/sdd-advisor-researcher.md +104 -0
- package/agents/sdd-assumptions-analyzer.md +105 -0
- package/agents/sdd-codebase-mapper.md +770 -0
- package/agents/sdd-debugger.md +1373 -0
- package/agents/sdd-executor.md +509 -0
- package/agents/sdd-integration-checker.md +443 -0
- package/agents/sdd-nyquist-auditor.md +176 -0
- package/agents/sdd-phase-researcher.md +698 -0
- package/agents/sdd-plan-checker.md +773 -0
- package/agents/sdd-planner.md +1354 -0
- package/agents/sdd-project-researcher.md +654 -0
- package/agents/sdd-research-synthesizer.md +247 -0
- package/agents/sdd-roadmapper.md +679 -0
- package/agents/sdd-ui-auditor.md +439 -0
- package/agents/sdd-ui-checker.md +300 -0
- package/agents/sdd-ui-researcher.md +357 -0
- package/agents/sdd-user-profiler.md +171 -0
- package/agents/sdd-verifier.md +700 -0
- package/bin/install.js +5009 -0
- package/commands/sdd/add-backlog.md +76 -0
- package/commands/sdd/add-phase.md +43 -0
- package/commands/sdd/add-tests.md +41 -0
- package/commands/sdd/add-todo.md +47 -0
- package/commands/sdd/audit-milestone.md +36 -0
- package/commands/sdd/audit-uat.md +24 -0
- package/commands/sdd/autonomous.md +41 -0
- package/commands/sdd/check-todos.md +45 -0
- package/commands/sdd/cleanup.md +18 -0
- package/commands/sdd/complete-milestone.md +136 -0
- package/commands/sdd/debug.md +173 -0
- package/commands/sdd/discuss-phase.md +64 -0
- package/commands/sdd/do.md +30 -0
- package/commands/sdd/execute-phase.md +59 -0
- package/commands/sdd/fast.md +30 -0
- package/commands/sdd/forensics.md +56 -0
- package/commands/sdd/health.md +22 -0
- package/commands/sdd/help.md +22 -0
- package/commands/sdd/insert-phase.md +32 -0
- package/commands/sdd/join-discord.md +18 -0
- package/commands/sdd/list-phase-assumptions.md +46 -0
- package/commands/sdd/list-workspaces.md +19 -0
- package/commands/sdd/manager.md +39 -0
- package/commands/sdd/map-codebase.md +71 -0
- package/commands/sdd/milestone-summary.md +51 -0
- package/commands/sdd/new-milestone.md +44 -0
- package/commands/sdd/new-project.md +42 -0
- package/commands/sdd/new-workspace.md +44 -0
- package/commands/sdd/next.md +24 -0
- package/commands/sdd/note.md +34 -0
- package/commands/sdd/pause-work.md +38 -0
- package/commands/sdd/plan-milestone-gaps.md +34 -0
- package/commands/sdd/plan-phase.md +47 -0
- package/commands/sdd/plant-seed.md +28 -0
- package/commands/sdd/pr-branch.md +25 -0
- package/commands/sdd/profile-user.md +46 -0
- package/commands/sdd/progress.md +24 -0
- package/commands/sdd/quick.md +47 -0
- package/commands/sdd/reapply-patches.md +123 -0
- package/commands/sdd/remove-phase.md +31 -0
- package/commands/sdd/remove-workspace.md +26 -0
- package/commands/sdd/research-phase.md +195 -0
- package/commands/sdd/resume-work.md +40 -0
- package/commands/sdd/review-backlog.md +61 -0
- package/commands/sdd/review.md +37 -0
- package/commands/sdd/session-report.md +19 -0
- package/commands/sdd/set-profile.md +12 -0
- package/commands/sdd/settings.md +36 -0
- package/commands/sdd/ship.md +23 -0
- package/commands/sdd/stats.md +18 -0
- package/commands/sdd/thread.md +127 -0
- package/commands/sdd/ui-phase.md +34 -0
- package/commands/sdd/ui-review.md +32 -0
- package/commands/sdd/update.md +37 -0
- package/commands/sdd/validate-phase.md +35 -0
- package/commands/sdd/verify-work.md +38 -0
- package/commands/sdd/workstreams.md +63 -0
- package/hooks/dist/sdd-check-update.js +114 -0
- package/hooks/dist/sdd-context-monitor.js +156 -0
- package/hooks/dist/sdd-prompt-guard.js +96 -0
- package/hooks/dist/sdd-statusline.js +119 -0
- package/hooks/dist/sdd-workflow-guard.js +94 -0
- package/package.json +55 -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/rebrand-gsd-to-sdd.sh +220 -0
- package/scripts/run-tests.cjs +29 -0
- package/scripts/secret-scan.sh +227 -0
- package/scripts/sync-upstream.sh +56 -0
- package/sdd/bin/lib/commands.cjs +959 -0
- package/sdd/bin/lib/config.cjs +442 -0
- package/sdd/bin/lib/core.cjs +1230 -0
- package/sdd/bin/lib/frontmatter.cjs +336 -0
- package/sdd/bin/lib/init.cjs +1442 -0
- package/sdd/bin/lib/milestone.cjs +252 -0
- package/sdd/bin/lib/model-profiles.cjs +68 -0
- package/sdd/bin/lib/phase.cjs +888 -0
- package/sdd/bin/lib/profile-output.cjs +952 -0
- package/sdd/bin/lib/profile-pipeline.cjs +539 -0
- package/sdd/bin/lib/roadmap.cjs +329 -0
- package/sdd/bin/lib/security.cjs +382 -0
- package/sdd/bin/lib/state.cjs +1031 -0
- package/sdd/bin/lib/template.cjs +222 -0
- package/sdd/bin/lib/uat.cjs +282 -0
- package/sdd/bin/lib/verify.cjs +888 -0
- package/sdd/bin/lib/workstream.cjs +491 -0
- package/sdd/bin/sdd-tools.cjs +918 -0
- package/sdd/commands/sdd/workstreams.md +63 -0
- package/sdd/references/checkpoints.md +778 -0
- package/sdd/references/continuation-format.md +249 -0
- package/sdd/references/decimal-phase-calculation.md +64 -0
- package/sdd/references/git-integration.md +295 -0
- package/sdd/references/git-planning-commit.md +38 -0
- package/sdd/references/model-profile-resolution.md +36 -0
- package/sdd/references/model-profiles.md +139 -0
- package/sdd/references/phase-argument-parsing.md +61 -0
- package/sdd/references/planning-config.md +202 -0
- package/sdd/references/questioning.md +162 -0
- package/sdd/references/tdd.md +263 -0
- package/sdd/references/ui-brand.md +160 -0
- package/sdd/references/user-profiling.md +681 -0
- package/sdd/references/verification-patterns.md +612 -0
- package/sdd/references/workstream-flag.md +58 -0
- package/sdd/templates/DEBUG.md +164 -0
- package/sdd/templates/UAT.md +265 -0
- package/sdd/templates/UI-SPEC.md +100 -0
- package/sdd/templates/VALIDATION.md +76 -0
- package/sdd/templates/claude-md.md +122 -0
- package/sdd/templates/codebase/architecture.md +255 -0
- package/sdd/templates/codebase/concerns.md +310 -0
- package/sdd/templates/codebase/conventions.md +307 -0
- package/sdd/templates/codebase/integrations.md +280 -0
- package/sdd/templates/codebase/stack.md +186 -0
- package/sdd/templates/codebase/structure.md +285 -0
- package/sdd/templates/codebase/testing.md +480 -0
- package/sdd/templates/config.json +44 -0
- package/sdd/templates/context.md +352 -0
- package/sdd/templates/continue-here.md +78 -0
- package/sdd/templates/copilot-instructions.md +7 -0
- package/sdd/templates/debug-subagent-prompt.md +91 -0
- package/sdd/templates/dev-preferences.md +21 -0
- package/sdd/templates/discovery.md +146 -0
- package/sdd/templates/discussion-log.md +63 -0
- package/sdd/templates/milestone-archive.md +123 -0
- package/sdd/templates/milestone.md +115 -0
- package/sdd/templates/phase-prompt.md +610 -0
- package/sdd/templates/planner-subagent-prompt.md +117 -0
- package/sdd/templates/project.md +186 -0
- package/sdd/templates/requirements.md +231 -0
- package/sdd/templates/research-project/ARCHITECTURE.md +204 -0
- package/sdd/templates/research-project/FEATURES.md +147 -0
- package/sdd/templates/research-project/PITFALLS.md +200 -0
- package/sdd/templates/research-project/STACK.md +120 -0
- package/sdd/templates/research-project/SUMMARY.md +170 -0
- package/sdd/templates/research.md +552 -0
- package/sdd/templates/retrospective.md +54 -0
- package/sdd/templates/roadmap.md +202 -0
- package/sdd/templates/state.md +176 -0
- package/sdd/templates/summary-complex.md +59 -0
- package/sdd/templates/summary-minimal.md +41 -0
- package/sdd/templates/summary-standard.md +48 -0
- package/sdd/templates/summary.md +248 -0
- package/sdd/templates/user-profile.md +146 -0
- package/sdd/templates/user-setup.md +311 -0
- package/sdd/templates/verification-report.md +322 -0
- package/sdd/workflows/add-phase.md +112 -0
- package/sdd/workflows/add-tests.md +351 -0
- package/sdd/workflows/add-todo.md +158 -0
- package/sdd/workflows/audit-milestone.md +340 -0
- package/sdd/workflows/audit-uat.md +109 -0
- package/sdd/workflows/autonomous.md +891 -0
- package/sdd/workflows/check-todos.md +177 -0
- package/sdd/workflows/cleanup.md +152 -0
- package/sdd/workflows/complete-milestone.md +767 -0
- package/sdd/workflows/diagnose-issues.md +231 -0
- package/sdd/workflows/discovery-phase.md +289 -0
- package/sdd/workflows/discuss-phase-assumptions.md +653 -0
- package/sdd/workflows/discuss-phase.md +1049 -0
- package/sdd/workflows/do.md +104 -0
- package/sdd/workflows/execute-phase.md +846 -0
- package/sdd/workflows/execute-plan.md +514 -0
- package/sdd/workflows/fast.md +105 -0
- package/sdd/workflows/forensics.md +265 -0
- package/sdd/workflows/health.md +181 -0
- package/sdd/workflows/help.md +606 -0
- package/sdd/workflows/insert-phase.md +130 -0
- package/sdd/workflows/list-phase-assumptions.md +178 -0
- package/sdd/workflows/list-workspaces.md +56 -0
- package/sdd/workflows/manager.md +362 -0
- package/sdd/workflows/map-codebase.md +377 -0
- package/sdd/workflows/milestone-summary.md +223 -0
- package/sdd/workflows/new-milestone.md +486 -0
- package/sdd/workflows/new-project.md +1250 -0
- package/sdd/workflows/new-workspace.md +237 -0
- package/sdd/workflows/next.md +97 -0
- package/sdd/workflows/node-repair.md +92 -0
- package/sdd/workflows/note.md +156 -0
- package/sdd/workflows/pause-work.md +176 -0
- package/sdd/workflows/plan-milestone-gaps.md +273 -0
- package/sdd/workflows/plan-phase.md +859 -0
- package/sdd/workflows/plant-seed.md +169 -0
- package/sdd/workflows/pr-branch.md +129 -0
- package/sdd/workflows/profile-user.md +450 -0
- package/sdd/workflows/progress.md +507 -0
- package/sdd/workflows/quick.md +757 -0
- package/sdd/workflows/remove-phase.md +155 -0
- package/sdd/workflows/remove-workspace.md +90 -0
- package/sdd/workflows/research-phase.md +82 -0
- package/sdd/workflows/resume-project.md +326 -0
- package/sdd/workflows/review.md +228 -0
- package/sdd/workflows/session-report.md +146 -0
- package/sdd/workflows/settings.md +283 -0
- package/sdd/workflows/ship.md +228 -0
- package/sdd/workflows/stats.md +60 -0
- package/sdd/workflows/transition.md +671 -0
- package/sdd/workflows/ui-phase.md +302 -0
- package/sdd/workflows/ui-review.md +165 -0
- package/sdd/workflows/update.md +323 -0
- package/sdd/workflows/validate-phase.md +174 -0
- package/sdd/workflows/verify-phase.md +254 -0
- package/sdd/workflows/verify-work.md +637 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sdd-hook-version: {{SDD_VERSION}}
|
|
3
|
+
// SDD Workflow Guard — PreToolUse hook
|
|
4
|
+
// Detects when Claude attempts file edits outside a SDD workflow context
|
|
5
|
+
// (no active /sdd: command or Task subagent) and injects an advisory warning.
|
|
6
|
+
//
|
|
7
|
+
// This is a SOFT guard — it advises, not blocks. The edit still proceeds.
|
|
8
|
+
// The warning nudges Claude to use /sdd:quick or /sdd:fast instead of
|
|
9
|
+
// making direct edits that bypass state tracking.
|
|
10
|
+
//
|
|
11
|
+
// Enable via config: hooks.workflow_guard: true (default: false)
|
|
12
|
+
// Only triggers on Write/Edit tool calls to non-.planning/ files.
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
let input = '';
|
|
18
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
19
|
+
process.stdin.setEncoding('utf8');
|
|
20
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
21
|
+
process.stdin.on('end', () => {
|
|
22
|
+
clearTimeout(stdinTimeout);
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(input);
|
|
25
|
+
const toolName = data.tool_name;
|
|
26
|
+
|
|
27
|
+
// Only guard Write and Edit tool calls
|
|
28
|
+
if (toolName !== 'Write' && toolName !== 'Edit') {
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if we're inside a SDD workflow (Task subagent or /sdd: command)
|
|
33
|
+
// Subagents have a session_id that differs from the parent
|
|
34
|
+
// and typically have a description field set by the orchestrator
|
|
35
|
+
if (data.tool_input?.is_subagent || data.session_type === 'task') {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check the file being edited
|
|
40
|
+
const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
41
|
+
|
|
42
|
+
// Allow edits to .planning/ files (SDD state management)
|
|
43
|
+
if (filePath.includes('.planning/') || filePath.includes('.planning\\')) {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Allow edits to common config/docs files that don't need SDD tracking
|
|
48
|
+
const allowedPatterns = [
|
|
49
|
+
/\.gitignore$/,
|
|
50
|
+
/\.env/,
|
|
51
|
+
/CLAUDE\.md$/,
|
|
52
|
+
/AGENTS\.md$/,
|
|
53
|
+
/GEMINI\.md$/,
|
|
54
|
+
/settings\.json$/,
|
|
55
|
+
];
|
|
56
|
+
if (allowedPatterns.some(p => p.test(filePath))) {
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if workflow guard is enabled
|
|
61
|
+
const cwd = data.cwd || process.cwd();
|
|
62
|
+
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
63
|
+
if (fs.existsSync(configPath)) {
|
|
64
|
+
try {
|
|
65
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
66
|
+
if (!config.hooks?.workflow_guard) {
|
|
67
|
+
process.exit(0); // Guard disabled (default)
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
process.exit(0); // No SDD project — don't guard
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If we get here: SDD project, guard enabled, file edit outside .planning/,
|
|
77
|
+
// not in a subagent context. Inject advisory warning.
|
|
78
|
+
const output = {
|
|
79
|
+
hookSpecificOutput: {
|
|
80
|
+
hookEventName: "PreToolUse",
|
|
81
|
+
additionalContext: `⚠️ WORKFLOW ADVISORY: You're editing ${path.basename(filePath)} directly without a SDD command. ` +
|
|
82
|
+
'This edit will not be tracked in STATE.md or produce a SUMMARY.md. ' +
|
|
83
|
+
'Consider using /sdd:fast for trivial fixes or /sdd:quick for larger changes ' +
|
|
84
|
+
'to maintain project state tracking. ' +
|
|
85
|
+
'If this is intentional (e.g., user explicitly asked for a direct edit), proceed normally.'
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
process.stdout.write(JSON.stringify(output));
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// Silent fail — never block tool execution
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bhargavvc/sdd-cc",
|
|
3
|
+
"version": "1.30.0",
|
|
4
|
+
"description": "A meta-prompting, context engineering and spec-driven development system for Claude Code, OpenCode, Gemini and Codex by TÂCHES.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"sdd-cc": "bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"commands",
|
|
11
|
+
"sdd",
|
|
12
|
+
"agents",
|
|
13
|
+
"hooks/dist",
|
|
14
|
+
"scripts"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"claude",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"ai",
|
|
20
|
+
"meta-prompting",
|
|
21
|
+
"context-engineering",
|
|
22
|
+
"spec-driven-development",
|
|
23
|
+
"gemini",
|
|
24
|
+
"gemini-cli",
|
|
25
|
+
"codex",
|
|
26
|
+
"codex-cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "TÂCHES",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/bhargavvc/sdd-cc.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/bhargavvc/sdd-cc",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/bhargavvc/sdd-cc/issues"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"c8": "^11.0.0",
|
|
43
|
+
"esbuild": "^0.24.0",
|
|
44
|
+
"vitest": "^4.1.2"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build:hooks": "node scripts/build-hooks.js",
|
|
48
|
+
"prepublishOnly": "npm run build:hooks",
|
|
49
|
+
"test": "node scripts/run-tests.cjs",
|
|
50
|
+
"test:coverage": "c8 --check-coverage --lines 70 --reporter text --include 'sdd/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -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 SDD 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
|
+
'sdd-check-update.js',
|
|
19
|
+
'sdd-context-monitor.js',
|
|
20
|
+
'sdd-prompt-guard.js',
|
|
21
|
+
'sdd-statusline.js',
|
|
22
|
+
'sdd-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
|
+
'sdd/bin/lib/security.cjs'
|
|
76
|
+
'hooks/sdd-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 "$@"
|