@cybedefend/vibedefend 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +77 -0
  2. package/README.md +120 -0
  3. package/bin/vibedefend.js +19 -0
  4. package/dist/auth/auth-store.js +170 -0
  5. package/dist/auth/auth-store.js.map +1 -0
  6. package/dist/auth/auth.js +125 -0
  7. package/dist/auth/auth.js.map +1 -0
  8. package/dist/auth/callback-server.js +216 -0
  9. package/dist/auth/callback-server.js.map +1 -0
  10. package/dist/auth/pkce.js +31 -0
  11. package/dist/auth/pkce.js.map +1 -0
  12. package/dist/auth/token-exchange.js +83 -0
  13. package/dist/auth/token-exchange.js.map +1 -0
  14. package/dist/clients/claude-code.js +170 -0
  15. package/dist/clients/claude-code.js.map +1 -0
  16. package/dist/clients/codex.js +378 -0
  17. package/dist/clients/codex.js.map +1 -0
  18. package/dist/clients/cursor-guards-rules.js +94 -0
  19. package/dist/clients/cursor-guards-rules.js.map +1 -0
  20. package/dist/clients/cursor.js +172 -0
  21. package/dist/clients/cursor.js.map +1 -0
  22. package/dist/clients/detect.js +86 -0
  23. package/dist/clients/detect.js.map +1 -0
  24. package/dist/clients/registry.js +41 -0
  25. package/dist/clients/registry.js.map +1 -0
  26. package/dist/clients/types.js +2 -0
  27. package/dist/clients/types.js.map +1 -0
  28. package/dist/clients/vscode.js +187 -0
  29. package/dist/clients/vscode.js.map +1 -0
  30. package/dist/clients/windsurf.js +151 -0
  31. package/dist/clients/windsurf.js.map +1 -0
  32. package/dist/config.js +32 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/custom-regions.js +112 -0
  35. package/dist/custom-regions.js.map +1 -0
  36. package/dist/diagnostics.js +122 -0
  37. package/dist/diagnostics.js.map +1 -0
  38. package/dist/doctor.js +125 -0
  39. package/dist/doctor.js.map +1 -0
  40. package/dist/guards-evaluator/bucketing.js +83 -0
  41. package/dist/guards-evaluator/bucketing.js.map +1 -0
  42. package/dist/guards-evaluator/evaluate.js +272 -0
  43. package/dist/guards-evaluator/evaluate.js.map +1 -0
  44. package/dist/guards-evaluator/glob.js +148 -0
  45. package/dist/guards-evaluator/glob.js.map +1 -0
  46. package/dist/guards-evaluator/index.js +9 -0
  47. package/dist/guards-evaluator/index.js.map +1 -0
  48. package/dist/guards-evaluator/preprocess.js +174 -0
  49. package/dist/guards-evaluator/preprocess.js.map +1 -0
  50. package/dist/guards-evaluator/redact.js +111 -0
  51. package/dist/guards-evaluator/redact.js.map +1 -0
  52. package/dist/guards-evaluator/regex.js +125 -0
  53. package/dist/guards-evaluator/regex.js.map +1 -0
  54. package/dist/guards-evaluator/types.js +2 -0
  55. package/dist/guards-evaluator/types.js.map +1 -0
  56. package/dist/guards-evaluator/validation.js +115 -0
  57. package/dist/guards-evaluator/validation.js.map +1 -0
  58. package/dist/hook-runner.js +6680 -0
  59. package/dist/hooks/install.js +169 -0
  60. package/dist/hooks/install.js.map +1 -0
  61. package/dist/hooks/runtime/api.js +167 -0
  62. package/dist/hooks/runtime/api.js.map +1 -0
  63. package/dist/hooks/runtime/config.js +60 -0
  64. package/dist/hooks/runtime/config.js.map +1 -0
  65. package/dist/hooks/runtime/emit.js +45 -0
  66. package/dist/hooks/runtime/emit.js.map +1 -0
  67. package/dist/hooks/runtime/fetch-rules.js +154 -0
  68. package/dist/hooks/runtime/fetch-rules.js.map +1 -0
  69. package/dist/hooks/runtime/guard-rules-cache.js +217 -0
  70. package/dist/hooks/runtime/guard-rules-cache.js.map +1 -0
  71. package/dist/hooks/runtime/guard-violations-buffer.js +105 -0
  72. package/dist/hooks/runtime/guard-violations-buffer.js.map +1 -0
  73. package/dist/hooks/runtime/pre-compact.js +41 -0
  74. package/dist/hooks/runtime/pre-compact.js.map +1 -0
  75. package/dist/hooks/runtime/resolve.js +206 -0
  76. package/dist/hooks/runtime/resolve.js.map +1 -0
  77. package/dist/hooks/runtime/session-review.js +198 -0
  78. package/dist/hooks/runtime/session-review.js.map +1 -0
  79. package/dist/hooks/runtime/session-start.js +101 -0
  80. package/dist/hooks/runtime/session-start.js.map +1 -0
  81. package/dist/hooks/runtime/sniff.js +112 -0
  82. package/dist/hooks/runtime/sniff.js.map +1 -0
  83. package/dist/hooks/runtime/types.js +22 -0
  84. package/dist/hooks/runtime/types.js.map +1 -0
  85. package/dist/hooks/runtime/user-prompt-submit.js +154 -0
  86. package/dist/hooks/runtime/user-prompt-submit.js.map +1 -0
  87. package/dist/index.js +129 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/install.js +183 -0
  90. package/dist/install.js.map +1 -0
  91. package/dist/login.js +335 -0
  92. package/dist/login.js.map +1 -0
  93. package/dist/prompts.js +134 -0
  94. package/dist/prompts.js.map +1 -0
  95. package/dist/self-update.js +177 -0
  96. package/dist/self-update.js.map +1 -0
  97. package/dist/status.js +58 -0
  98. package/dist/status.js.map +1 -0
  99. package/dist/utils.js +84 -0
  100. package/dist/utils.js.map +1 -0
  101. package/dist/version.js +23 -0
  102. package/dist/version.js.map +1 -0
  103. package/package.json +73 -0
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Core evaluator — pure function evaluate(rules, request) → Decision.
3
+ *
4
+ * Algorithm:
5
+ * 1. Filter rules with action === 'off'.
6
+ * 2. Bucket the request's target type.
7
+ * 3. Walk rules in that bucket; for each: check toolMatch, then per-target matchers.
8
+ * 4. Collect matched rules; sort by severity descending.
9
+ * 5. If any matched rule has action=deny → verdict=deny, message from highest-severity deny.
10
+ * 6. Else if any has action=warn → verdict=warn, message from highest-severity warn.
11
+ * 7. Else → verdict=allow.
12
+ */
13
+ import { matchAnyGlob, matchAllGlobExcludes, matchGlob } from './glob.js';
14
+ import { compileRegex } from './regex.js';
15
+ import { bucketRules, targetTypeBucket } from './bucketing.js';
16
+ import { stripDataArgs } from './preprocess.js';
17
+ // -----------------------------------------------------------------------
18
+ // Severity ordering
19
+ // -----------------------------------------------------------------------
20
+ const SEVERITY_ORDER = {
21
+ critical: 4,
22
+ high: 3,
23
+ medium: 2,
24
+ low: 1,
25
+ };
26
+ function compareSeverityDesc(a, b) {
27
+ return SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity];
28
+ }
29
+ // -----------------------------------------------------------------------
30
+ // Message formatter
31
+ // -----------------------------------------------------------------------
32
+ /**
33
+ * Format a message template substituting {target} and {rule_name}.
34
+ *
35
+ * {target} → human-readable description of the target
36
+ * {rule_name} → rule.name if set, else rule.id
37
+ */
38
+ export function formatMessage(template, target, rule) {
39
+ let targetStr;
40
+ switch (target.type) {
41
+ case 'file':
42
+ targetStr = target.path;
43
+ break;
44
+ case 'command':
45
+ targetStr = target.command;
46
+ break;
47
+ case 'env':
48
+ targetStr = target.name;
49
+ break;
50
+ case 'http':
51
+ targetStr = target.url;
52
+ break;
53
+ case 'git':
54
+ targetStr = `git ${target.subcommand}`;
55
+ break;
56
+ }
57
+ const ruleName = rule.name ?? rule.id;
58
+ return template
59
+ .replace(/\{target\}/g, targetStr)
60
+ .replace(/\{rule_name\}/g, ruleName);
61
+ }
62
+ // -----------------------------------------------------------------------
63
+ // Per-target matchers
64
+ // -----------------------------------------------------------------------
65
+ function matchesFile(rule, target, homeDir) {
66
+ const opts = { homeDir };
67
+ const globsSet = rule.filePathGlobs && rule.filePathGlobs.length > 0;
68
+ if (globsSet) {
69
+ if (!matchAnyGlob(rule.filePathGlobs, target.path, opts))
70
+ return false;
71
+ }
72
+ if (!matchAllGlobExcludes(rule.filePathExcludes, target.path, opts))
73
+ return false;
74
+ // If no globs at all (only excludes or nothing), treat as "any" only if at
75
+ // least one matcher is set.
76
+ if (!globsSet)
77
+ return false;
78
+ return true;
79
+ }
80
+ function matchesCommand(rule, target, homeDir) {
81
+ const opts = { homeDir };
82
+ const hasRegex = !!rule.commandRegex;
83
+ const hasArgGlobs = rule.commandArgGlobs && rule.commandArgGlobs.length > 0;
84
+ if (!hasRegex && !hasArgGlobs)
85
+ return false;
86
+ // Reconstruct full command string for regex matching (command + args joined).
87
+ // Then strip data-bearing arg values (e.g. `git commit -m "..."`, `gh pr
88
+ // --body "..."`) so the regex doesn't false-positive on user-provided text
89
+ // that happens to mention a dangerous keyword. See preprocess.ts header for
90
+ // the full rationale.
91
+ //
92
+ // CRITICAL: pass the argv-tuple to stripDataArgs directly (not the joined
93
+ // string). Naively joining `[cmd, ...argv].join(' ')` LOSES quote boundaries
94
+ // — a message arg "fix: drop table users" becomes 4 separate tokens, and
95
+ // the preprocessor only strips the first one. Passing the already-tokenized
96
+ // argv preserves each argument as one indivisible token regardless of
97
+ // whether it contains spaces.
98
+ const fullCommand = stripDataArgs([target.command, ...target.argv]);
99
+ if (hasRegex) {
100
+ // A single malformed rule (e.g. an unsupported `(?i)` inline flag rejected
101
+ // by native RegExp when re2-wasm isn't available) MUST NOT poison the
102
+ // evaluation of every other rule in the bucket. Swallow per-rule compile
103
+ // failures: log to stderr once so operators see the culprit, treat the
104
+ // rule as non-matching, and continue. The other rules still enforce.
105
+ try {
106
+ const re = compileRegex(rule.commandRegex);
107
+ if (!re.test(fullCommand))
108
+ return false;
109
+ }
110
+ catch (e) {
111
+ const msg = e instanceof Error ? e.message : String(e);
112
+ // eslint-disable-next-line no-console
113
+ console.error(`[guards-evaluator] Skipping rule ${rule.id} — invalid commandRegex: ${msg}`);
114
+ return false;
115
+ }
116
+ }
117
+ if (hasArgGlobs) {
118
+ // Every glob in commandArgGlobs must match at least one entry of argv
119
+ for (const glob of rule.commandArgGlobs) {
120
+ const matchedAny = target.argv.some((arg) => matchGlob(glob, arg, opts));
121
+ if (!matchedAny)
122
+ return false;
123
+ }
124
+ }
125
+ return true;
126
+ }
127
+ function matchesEnv(rule, target, homeDir) {
128
+ return matchAnyGlob(rule.envNameGlobs, target.name, { homeDir });
129
+ }
130
+ function matchesHttp(rule, target, homeDir) {
131
+ const opts = { homeDir };
132
+ // Parse hostname and path from URL
133
+ let hostname;
134
+ let pathname;
135
+ try {
136
+ const u = new URL(target.url);
137
+ hostname = u.hostname;
138
+ pathname = u.pathname;
139
+ }
140
+ catch {
141
+ // Fallback: treat entire url as hostname
142
+ hostname = target.url;
143
+ pathname = '';
144
+ }
145
+ // httpHostGlobs — at least one must match
146
+ const hostGlobsSet = rule.httpHostGlobs && rule.httpHostGlobs.length > 0;
147
+ if (hostGlobsSet) {
148
+ if (!matchAnyGlob(rule.httpHostGlobs, hostname, opts))
149
+ return false;
150
+ }
151
+ // httpHostExcludes — none must match
152
+ if (!matchAllGlobExcludes(rule.httpHostExcludes, hostname, opts))
153
+ return false;
154
+ // httpMethod — if set, method must be in the list
155
+ if (rule.httpMethod && rule.httpMethod.length > 0) {
156
+ if (!rule.httpMethod.includes(target.method.toUpperCase()))
157
+ return false;
158
+ }
159
+ // httpPathGlobs — if set, at least one must match the path
160
+ if (rule.httpPathGlobs && rule.httpPathGlobs.length > 0) {
161
+ if (!matchAnyGlob(rule.httpPathGlobs, pathname, opts))
162
+ return false;
163
+ }
164
+ // Must have at least one positive matcher
165
+ if (!hostGlobsSet)
166
+ return false;
167
+ return true;
168
+ }
169
+ function matchesGit(rule, target, homeDir) {
170
+ const opts = { homeDir };
171
+ // gitSubcommand — if set, must include target.subcommand
172
+ if (rule.gitSubcommand && rule.gitSubcommand.length > 0) {
173
+ if (!rule.gitSubcommand.includes(target.subcommand))
174
+ return false;
175
+ }
176
+ // gitRemoteGlobs — if set, target.remote must match at least one
177
+ if (rule.gitRemoteGlobs && rule.gitRemoteGlobs.length > 0) {
178
+ if (!target.remote)
179
+ return false;
180
+ if (!matchAnyGlob(rule.gitRemoteGlobs, target.remote, opts))
181
+ return false;
182
+ }
183
+ // gitBranchGlobs — if set, target.branch must match at least one
184
+ if (rule.gitBranchGlobs && rule.gitBranchGlobs.length > 0) {
185
+ if (!target.branch)
186
+ return false;
187
+ if (!matchAnyGlob(rule.gitBranchGlobs, target.branch, opts))
188
+ return false;
189
+ }
190
+ // Must have at least one matcher to be useful
191
+ const hasAny = (rule.gitSubcommand && rule.gitSubcommand.length > 0) ||
192
+ (rule.gitRemoteGlobs && rule.gitRemoteGlobs.length > 0) ||
193
+ (rule.gitBranchGlobs && rule.gitBranchGlobs.length > 0);
194
+ if (!hasAny)
195
+ return false;
196
+ return true;
197
+ }
198
+ function ruleMatches(rule, req) {
199
+ // toolMatch filter
200
+ if (rule.toolMatch && rule.toolMatch.length > 0) {
201
+ if (!rule.toolMatch.includes(req.tool))
202
+ return false;
203
+ }
204
+ const { target, homeDir } = req;
205
+ switch (target.type) {
206
+ case 'file': return matchesFile(rule, target, homeDir);
207
+ case 'command': return matchesCommand(rule, target, homeDir);
208
+ case 'env': return matchesEnv(rule, target, homeDir);
209
+ case 'http': return matchesHttp(rule, target, homeDir);
210
+ case 'git': return matchesGit(rule, target, homeDir);
211
+ }
212
+ }
213
+ // -----------------------------------------------------------------------
214
+ // Public evaluator
215
+ // -----------------------------------------------------------------------
216
+ /**
217
+ * Evaluate a list of effective rules against a guard request.
218
+ *
219
+ * Rules with `action === 'off'` are silently ignored.
220
+ * Matching rules are sorted by severity descending; deny always beats warn.
221
+ */
222
+ export function evaluate(rules, req) {
223
+ try {
224
+ // 1. Filter off rules
225
+ const active = rules.filter((r) => r.action !== 'off');
226
+ // 2. Bucket by target type
227
+ const bucketed = bucketRules(active);
228
+ const bucket = targetTypeBucket(req.target);
229
+ const candidates = bucketed[bucket];
230
+ // 3. Match each candidate
231
+ const matched = [];
232
+ for (const rule of candidates) {
233
+ if (ruleMatches(rule, req)) {
234
+ matched.push(rule);
235
+ }
236
+ }
237
+ if (matched.length === 0) {
238
+ return { verdict: 'allow', matchedRuleIds: [], message: undefined, maxSeverity: null };
239
+ }
240
+ // 4. Sort by severity descending
241
+ matched.sort(compareSeverityDesc);
242
+ const matchedRuleIds = matched.map((r) => r.id);
243
+ const maxSeverity = matched[0].severity;
244
+ // 5. Determine verdict
245
+ const denyRules = matched.filter((r) => r.action === 'deny');
246
+ if (denyRules.length > 0) {
247
+ const topDeny = denyRules[0]; // already sorted
248
+ const template = topDeny.denyMessage ?? 'Action denied by rule {rule_name} on {target}.';
249
+ const message = formatMessage(template, req.target, topDeny);
250
+ return { verdict: 'deny', matchedRuleIds, message, maxSeverity };
251
+ }
252
+ const warnRules = matched.filter((r) => r.action === 'warn');
253
+ if (warnRules.length > 0) {
254
+ const topWarn = warnRules[0];
255
+ const template = topWarn.warnMessage ?? 'Action warned by rule {rule_name} on {target}.';
256
+ const message = formatMessage(template, req.target, topWarn);
257
+ return { verdict: 'warn', matchedRuleIds, message, maxSeverity };
258
+ }
259
+ // All matched rules are 'off' (shouldn't happen after filtering, but be safe)
260
+ return { verdict: 'allow', matchedRuleIds: [], message: undefined, maxSeverity: null };
261
+ }
262
+ catch (e) {
263
+ const message = e instanceof Error ? e.message : 'unknown evaluator error';
264
+ return {
265
+ verdict: 'deny',
266
+ matchedRuleIds: [],
267
+ message: `Guard evaluator failed: ${message}. Failing closed — refuse the action.`,
268
+ maxSeverity: 'critical',
269
+ };
270
+ }
271
+ }
272
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/guards-evaluator/evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Glob matching implementation for VibeDefend Action Guards.
3
+ *
4
+ * Supports: * (no path sep), ** (any chars including /), ? (single char),
5
+ * [abc] character classes, ~ expansion (home dir only), path separator
6
+ * normalisation.
7
+ */
8
+ /**
9
+ * Expand ~ at the start of a pattern to opts.homeDir.
10
+ */
11
+ function expandHome(pattern, homeDir) {
12
+ if (pattern === '~')
13
+ return homeDir;
14
+ if (pattern.startsWith('~/'))
15
+ return homeDir + pattern.slice(1);
16
+ return pattern;
17
+ }
18
+ /**
19
+ * Normalise path separators (Windows backslashes → forward slashes).
20
+ */
21
+ function normaliseSep(s) {
22
+ return s.replace(/\\/g, '/');
23
+ }
24
+ /**
25
+ * Escape a literal string for inclusion in a regex.
26
+ */
27
+ function escapeRegex(s) {
28
+ return s.replace(/[.+^${}()|[\]\\]/g, '\\$&');
29
+ }
30
+ /**
31
+ * Convert a glob pattern (after ~ expansion + sep normalisation) to a RegExp.
32
+ *
33
+ * Rules:
34
+ * ** → matches any sequence of characters including '/' (greedy)
35
+ * * → matches any sequence of characters except '/'
36
+ * ? → matches any single character except '/'
37
+ * [abc] → character class (passed through as-is)
38
+ */
39
+ function globToRegex(pattern) {
40
+ let src = '';
41
+ let i = 0;
42
+ while (i < pattern.length) {
43
+ const c = pattern[i];
44
+ if (c === '*') {
45
+ if (pattern[i + 1] === '*') {
46
+ // ** — matches everything including slashes
47
+ // consume any surrounding slashes in pattern to avoid double-//
48
+ const before = src.endsWith('/') ? src.slice(0, -1) : src;
49
+ let j = i + 2;
50
+ while (j < pattern.length && pattern[j] === '/')
51
+ j++;
52
+ if (i === 0 && j === pattern.length) {
53
+ // just ** alone — matches anything
54
+ src = before + '.*';
55
+ }
56
+ else if (i === 0) {
57
+ // **/ at start
58
+ src = before + '(?:.*\\/)?';
59
+ }
60
+ else if (j >= pattern.length) {
61
+ // /** at end
62
+ src = before + '(?:\\/.*)?';
63
+ }
64
+ else {
65
+ // /**/ in the middle
66
+ src = before + '(?:\\/.*\\/|\\/|\\/)';
67
+ // Actually: /**/foo should match /foo and /a/b/foo
68
+ // We handle this more carefully:
69
+ src = before + '(?:\\/|(?:\\/.+\\/))';
70
+ }
71
+ i = j;
72
+ continue;
73
+ }
74
+ else {
75
+ // single * — no path separator
76
+ src += '[^/]*';
77
+ i++;
78
+ continue;
79
+ }
80
+ }
81
+ else if (c === '?') {
82
+ src += '[^/]';
83
+ i++;
84
+ }
85
+ else if (c === '[') {
86
+ // pass character class through
87
+ let j = i + 1;
88
+ let cls = '[';
89
+ if (j < pattern.length && pattern[j] === '!') {
90
+ cls += '^';
91
+ j++;
92
+ }
93
+ if (j < pattern.length && pattern[j] === ']') {
94
+ cls += '\\]';
95
+ j++;
96
+ }
97
+ while (j < pattern.length && pattern[j] !== ']') {
98
+ cls += escapeRegex(pattern[j]);
99
+ j++;
100
+ }
101
+ cls += ']';
102
+ src += cls;
103
+ i = j + 1;
104
+ }
105
+ else {
106
+ src += escapeRegex(c);
107
+ i++;
108
+ }
109
+ }
110
+ return new RegExp('^' + src + '$');
111
+ }
112
+ /**
113
+ * Match a single glob pattern against a value.
114
+ *
115
+ * @param pattern Glob pattern (may start with ~)
116
+ * @param value The string to test (file path, host, etc.)
117
+ * @param opts Must include homeDir for ~ expansion
118
+ */
119
+ export function matchGlob(pattern, value, opts) {
120
+ const expanded = expandHome(normaliseSep(pattern), normaliseSep(opts.homeDir));
121
+ const normValue = normaliseSep(value);
122
+ // A pattern without any glob characters — exact match
123
+ const hasGlob = /[*?[\]]/.test(expanded);
124
+ if (!hasGlob) {
125
+ return expanded === normValue;
126
+ }
127
+ const re = globToRegex(expanded);
128
+ return re.test(normValue);
129
+ }
130
+ /**
131
+ * Returns true if `value` matches ANY of the `patterns`.
132
+ * Returns false if patterns is undefined or empty.
133
+ */
134
+ export function matchAnyGlob(patterns, value, opts) {
135
+ if (!patterns || patterns.length === 0)
136
+ return false;
137
+ return patterns.some((p) => matchGlob(p, value, opts));
138
+ }
139
+ /**
140
+ * Returns true if `value` does NOT match any of the `excludes`.
141
+ * If excludes is undefined or empty, always returns true (nothing is excluded).
142
+ */
143
+ export function matchAllGlobExcludes(excludes, value, opts) {
144
+ if (!excludes || excludes.length === 0)
145
+ return true;
146
+ return !excludes.some((p) => matchGlob(p, value, opts));
147
+ }
148
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../../src/guards-evaluator/glob.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export * from './types.js';
2
+ export { evaluate, formatMessage } from './evaluate.js';
3
+ export { validateRule } from './validation.js';
4
+ export { redactTarget, hashTarget } from './redact.js';
5
+ export { matchGlob, matchAnyGlob, matchAllGlobExcludes } from './glob.js';
6
+ export { compileRegex, isRegexSafe, isRe2Available, waitForRe2 } from './regex.js';
7
+ export { targetTypeBucket, ruleBuckets, bucketRules } from './bucketing.js';
8
+ export { stripDataArgs } from './preprocess.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/guards-evaluator/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Quote-aware preprocessing of shell command strings.
3
+ *
4
+ * Problem: the rule-evaluation regexes scan the full command string for
5
+ * dangerous patterns. They have no awareness of shell semantics, so they
6
+ * cannot tell that the literal text inside a `git commit -m "..."` argument
7
+ * is DATA (a commit message) rather than CODE. This produces false positives
8
+ * — a perfectly safe commit message saying "fix: close .env bypass" trips
9
+ * the secret-read rule because the regex sees `.env` as a substring.
10
+ *
11
+ * Fix: before running the rule regex, tokenize the command with `shell-quote`
12
+ * (a pure-JS bash-grammar tokenizer that respects single/double quotes,
13
+ * escapes, and shell operators). For known data-bearing flags — `-m`/`-F`/
14
+ * `--message`/`--file`/`--body` on `git commit|tag|notes|stash` and `gh
15
+ * pr|issue create` — replace the value token with an opaque placeholder
16
+ * so the regex never sees the user-provided text.
17
+ *
18
+ * Scope is INTENTIONALLY narrow:
19
+ * - Only flags that take freeform USER TEXT (commit messages, PR bodies)
20
+ * get their values stripped. `bash -c "..."` does NOT get stripped —
21
+ * that arg IS code, not data.
22
+ * - Only the subcommands that genuinely accept message text. `git push
23
+ * -m` (not a real flag) keeps its argument so we don't lose detection
24
+ * on hypothetical future variants.
25
+ * - Stripping resets at every shell operator (`;`, `&&`, `||`, `|`).
26
+ * So `git commit -m "msg"; cat .env` strips the message but keeps
27
+ * `cat .env` for evaluation.
28
+ *
29
+ * No I/O, no system access — pure string in, pure string out. Parse failures
30
+ * (unclosed quotes, invalid escapes) fall back to the original input so
31
+ * the scanner keeps running.
32
+ */
33
+ import { parse as shellParse } from 'shell-quote';
34
+ /** Opaque placeholder substituted for stripped data arguments. */
35
+ const PLACEHOLDER = '<DATA>';
36
+ /**
37
+ * Subcommands that accept user-provided message TEXT as a `-m` / `-F` /
38
+ * `--message` / `--file` value. Other subcommands of the same tool either
39
+ * don't accept these flags or use them differently (e.g. `git push` has
40
+ * no `-m`, `git diff` doesn't either).
41
+ *
42
+ * Keyed by top-level command. Empty set means "always treat as data" (used
43
+ * for commands where every -m/-F is a message regardless of subcommand,
44
+ * e.g. `gh`).
45
+ */
46
+ const DATA_BEARING_CONTEXTS = {
47
+ git: new Set(['commit', 'tag', 'notes', 'stash', 'merge', 'cherry-pick', 'revert']),
48
+ gh: null, // gh pr/issue create/edit all accept --body
49
+ hg: new Set(['commit', 'tag']), // mercurial
50
+ };
51
+ /** Flags whose VALUE is data (skip the next token). */
52
+ const DATA_FLAGS_VALUE = new Set([
53
+ '-m', '-F',
54
+ '--message', '--file',
55
+ '--body', '--body-file',
56
+ ]);
57
+ /** Flags in `--flag=value` form whose value is data. */
58
+ const DATA_FLAGS_EQUAL = ['--message=', '--file=', '--body=', '--body-file='];
59
+ /**
60
+ * Strip data-argument values from a shell command.
61
+ *
62
+ * Accepts either a raw command STRING (legacy callers; will be tokenized
63
+ * with shell-quote, which may lose quote boundaries on free-form input) or
64
+ * a pre-tokenized ARGV ARRAY (preferred; quote boundaries already
65
+ * established by the caller's own tokenizer). The argv path is what the
66
+ * production evaluator uses — each argv element is treated as one
67
+ * indivisible token, so a message arg with spaces stays one token and gets
68
+ * cleanly replaced by `<DATA>`.
69
+ *
70
+ * Returns the (possibly modified) command as a single string. The caller's
71
+ * regex then sees the rest of the command unchanged — operators, other
72
+ * args, subsequent commands — so genuine code patterns still match.
73
+ *
74
+ * Never throws. Parse failures return the original input so the scanner
75
+ * still runs (fail-open at this layer; the per-rule regex is the gate).
76
+ */
77
+ export function stripDataArgs(command) {
78
+ if (!command || (Array.isArray(command) && command.length === 0)) {
79
+ return Array.isArray(command) ? '' : command;
80
+ }
81
+ let tokens;
82
+ if (Array.isArray(command)) {
83
+ // Caller already tokenized — each element is one indivisible token.
84
+ // Skip shell-quote so we don't accidentally re-split a message arg
85
+ // that contains spaces. Operators in argv (rare; the caller's tokenizer
86
+ // would normally absorb them into the command string) are passed through
87
+ // as bare strings — they won't match our data-flag detection anyway.
88
+ tokens = command.slice();
89
+ }
90
+ else {
91
+ try {
92
+ // CRITICAL: pass an env function that preserves `$VAR` references as
93
+ // their literal form. shell-quote's default behavior is to substitute
94
+ // variable references with empty strings, which would DROP `$HOME`,
95
+ // `$SECRET_KEY`, etc. from the command — breaking every rule that
96
+ // matches on those literals (e.g. shell.env.echo_secret looks for
97
+ // `echo $SECRET_KEY`, fs.delete.root_or_home matches `rm -rf $HOME`).
98
+ // Returning `$<name>` keeps the regex's view of the command identical
99
+ // to what the user typed.
100
+ tokens = shellParse(command, (key) => '$' + key);
101
+ }
102
+ catch {
103
+ return command;
104
+ }
105
+ }
106
+ // Walk tokens, tracking the current segment's top-level command and
107
+ // subcommand so we know whether a `-m` is a data flag here.
108
+ const out = [];
109
+ let segmentTopCmd = null;
110
+ let segmentSubCmd = null;
111
+ let positionInSegment = 0;
112
+ for (let i = 0; i < tokens.length; i++) {
113
+ const tok = tokens[i];
114
+ if (typeof tok === 'object' && 'op' in tok) {
115
+ // Shell operator (`;`, `&&`, `||`, `|`, `>`, `<`, etc.) — segment boundary.
116
+ out.push(tok.op);
117
+ segmentTopCmd = null;
118
+ segmentSubCmd = null;
119
+ positionInSegment = 0;
120
+ continue;
121
+ }
122
+ if (typeof tok !== 'string') {
123
+ // Patterns / comments / other object tokens — preserve literal form.
124
+ out.push(JSON.stringify(tok));
125
+ continue;
126
+ }
127
+ // First non-operator token in a segment is the command name.
128
+ if (positionInSegment === 0) {
129
+ segmentTopCmd = tok;
130
+ out.push(tok);
131
+ positionInSegment++;
132
+ continue;
133
+ }
134
+ // Second token might be the subcommand.
135
+ if (positionInSegment === 1) {
136
+ segmentSubCmd = tok;
137
+ }
138
+ const contextSubcommands = segmentTopCmd
139
+ ? DATA_BEARING_CONTEXTS[segmentTopCmd]
140
+ : undefined;
141
+ const inDataBearingContext = contextSubcommands === null ||
142
+ (contextSubcommands !== undefined &&
143
+ segmentSubCmd !== null &&
144
+ contextSubcommands.has(segmentSubCmd));
145
+ if (inDataBearingContext) {
146
+ // -m "msg" / -F file / --message msg / --file path
147
+ if (DATA_FLAGS_VALUE.has(tok)) {
148
+ out.push(tok);
149
+ if (i + 1 < tokens.length) {
150
+ const next = tokens[i + 1];
151
+ if (typeof next === 'string') {
152
+ out.push(PLACEHOLDER);
153
+ i++;
154
+ positionInSegment++;
155
+ continue;
156
+ }
157
+ }
158
+ positionInSegment++;
159
+ continue;
160
+ }
161
+ // --message=msg / --file=path / --body=...
162
+ const equalPrefix = DATA_FLAGS_EQUAL.find((p) => tok.startsWith(p));
163
+ if (equalPrefix) {
164
+ out.push(equalPrefix + PLACEHOLDER);
165
+ positionInSegment++;
166
+ continue;
167
+ }
168
+ }
169
+ out.push(tok);
170
+ positionInSegment++;
171
+ }
172
+ return out.join(' ');
173
+ }
174
+ //# sourceMappingURL=preprocess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preprocess.js","sourceRoot":"","sources":["../../src/guards-evaluator/preprocess.ts"],"names":[],"mappings":"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"}