@wrongstack/plugins 0.295.0 → 0.296.2

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 (110) hide show
  1. package/dist/accessibility-auditor/index.d.ts.map +1 -1
  2. package/dist/accessibility-auditor.js +31 -3
  3. package/dist/agent-handoff/index.d.ts.map +1 -1
  4. package/dist/agent-handoff.js +30 -1
  5. package/dist/auto-doc/index.d.ts.map +1 -1
  6. package/dist/auto-doc.js +3 -1
  7. package/dist/auto-i18n-extractor.js +5 -0
  8. package/dist/branch-guard/index.d.ts.map +1 -1
  9. package/dist/branch-guard.js +116 -3
  10. package/dist/changelog-writer/index.d.ts.map +1 -1
  11. package/dist/changelog-writer.js +137 -3
  12. package/dist/checkpoint/index.d.ts +2 -0
  13. package/dist/checkpoint/index.d.ts.map +1 -1
  14. package/dist/checkpoint.js +34 -8
  15. package/dist/code-metrics.js +5 -0
  16. package/dist/commit-validator/index.d.ts.map +1 -1
  17. package/dist/commit-validator.js +35 -8
  18. package/dist/context-pins/index.d.ts.map +1 -1
  19. package/dist/context-pins.js +6 -5
  20. package/dist/cost-tracker/index.d.ts.map +1 -1
  21. package/dist/cost-tracker.js +101 -1
  22. package/dist/cron/index.d.ts.map +1 -1
  23. package/dist/cron.js +1 -0
  24. package/dist/dead-code-detector/index.d.ts.map +1 -1
  25. package/dist/dead-code-detector.js +9 -1
  26. package/dist/dependency-vulnerability-gate/index.d.ts.map +1 -1
  27. package/dist/dependency-vulnerability-gate.js +43 -8
  28. package/dist/diff-summary/index.d.ts.map +1 -1
  29. package/dist/diff-summary.js +112 -2
  30. package/dist/doc-sync-guard.js +17 -1
  31. package/dist/duplicate-code-detector/index.d.ts.map +1 -1
  32. package/dist/duplicate-code-detector.js +102 -2
  33. package/dist/feature-flag-tracker/index.d.ts.map +1 -1
  34. package/dist/feature-flag-tracker.js +30 -4
  35. package/dist/file-watcher/index.d.ts.map +1 -1
  36. package/dist/file-watcher.js +6 -7
  37. package/dist/format-on-save/index.d.ts +2 -0
  38. package/dist/format-on-save/index.d.ts.map +1 -1
  39. package/dist/format-on-save.js +288 -59
  40. package/dist/git-autocommit/index.d.ts +12 -0
  41. package/dist/git-autocommit/index.d.ts.map +1 -1
  42. package/dist/git-autocommit.js +58 -4
  43. package/dist/import-organizer/index.d.ts.map +1 -1
  44. package/dist/import-organizer.js +152 -21
  45. package/dist/index.js +1413 -590
  46. package/dist/injection-shield/index.d.ts +16 -0
  47. package/dist/injection-shield/index.d.ts.map +1 -1
  48. package/dist/injection-shield.js +12 -4
  49. package/dist/interface-contract-guard/index.d.ts.map +1 -1
  50. package/dist/interface-contract-guard.js +60 -26
  51. package/dist/knowledge-graph/index.d.ts.map +1 -1
  52. package/dist/knowledge-graph.js +7 -6
  53. package/dist/lint-gate/index.d.ts.map +1 -1
  54. package/dist/lint-gate.js +115 -2
  55. package/dist/loop-breaker/index.d.ts.map +1 -1
  56. package/dist/loop-breaker.js +15 -5
  57. package/dist/migration-planner.js +15 -1
  58. package/dist/notify-hub/index.d.ts.map +1 -1
  59. package/dist/notify-hub.js +41 -4
  60. package/dist/pr-drafter/index.d.ts.map +1 -1
  61. package/dist/pr-drafter.js +20 -1
  62. package/dist/prompt-firewall/index.d.ts.map +1 -1
  63. package/dist/prompt-firewall.js +140 -11
  64. package/dist/refactor-suggester/index.d.ts.map +1 -1
  65. package/dist/refactor-suggester.js +119 -4
  66. package/dist/runtime/bounded-map.d.ts +86 -0
  67. package/dist/runtime/bounded-map.d.ts.map +1 -0
  68. package/dist/runtime/credential-patterns.d.ts +42 -0
  69. package/dist/runtime/credential-patterns.d.ts.map +1 -0
  70. package/dist/runtime/handles.d.ts +46 -0
  71. package/dist/runtime/handles.d.ts.map +1 -0
  72. package/dist/runtime/index.d.ts +4 -0
  73. package/dist/runtime/index.d.ts.map +1 -1
  74. package/dist/runtime/local-bin.d.ts +120 -0
  75. package/dist/runtime/local-bin.d.ts.map +1 -0
  76. package/dist/runtime/safe-json.d.ts +25 -0
  77. package/dist/runtime/safe-json.d.ts.map +1 -0
  78. package/dist/runtime.js +309 -22
  79. package/dist/schema-evolution-guard.js +5 -0
  80. package/dist/secret-scanner/index.d.ts.map +1 -1
  81. package/dist/secret-scanner.js +194 -50
  82. package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
  83. package/dist/security-hotspot-scanner.js +132 -1
  84. package/dist/semantic-search-indexer/index.d.ts.map +1 -1
  85. package/dist/semantic-search-indexer.js +7 -1
  86. package/dist/semver-bump/index.d.ts.map +1 -1
  87. package/dist/semver-bump.js +21 -2
  88. package/dist/session-recap/index.d.ts.map +1 -1
  89. package/dist/session-recap.js +18 -1
  90. package/dist/smart-rename/index.d.ts +2 -0
  91. package/dist/smart-rename/index.d.ts.map +1 -1
  92. package/dist/smart-rename.js +16 -2
  93. package/dist/spec-linker/index.d.ts.map +1 -1
  94. package/dist/spec-linker.js +19 -2
  95. package/dist/template-engine/index.d.ts.map +1 -1
  96. package/dist/template-engine.js +45 -3
  97. package/dist/test-coverage-gate.js +17 -1
  98. package/dist/test-runner-gate/index.d.ts.map +1 -1
  99. package/dist/test-runner-gate.js +142 -9
  100. package/dist/todo-listener/index.d.ts.map +1 -1
  101. package/dist/todo-listener.js +42 -2
  102. package/dist/todo-tracker/index.d.ts.map +1 -1
  103. package/dist/todo-tracker.js +3 -4
  104. package/dist/token-budget/index.d.ts.map +1 -1
  105. package/dist/token-budget.js +31 -19
  106. package/dist/token-throttle/index.d.ts.map +1 -1
  107. package/dist/token-throttle.js +17 -2
  108. package/dist/type-gate/index.d.ts.map +1 -1
  109. package/dist/type-gate.js +108 -17
  110. package/package.json +3 -3
@@ -1,20 +1,61 @@
1
- // src/secret-scanner/index.ts
2
- var BASE_PATTERNS = [
1
+ // src/runtime/local-bin.ts
2
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
3
+
4
+ // src/runtime/handles.ts
5
+ function releaseHandle(off) {
6
+ if (off) {
7
+ try {
8
+ off();
9
+ } catch {
10
+ }
11
+ }
12
+ return null;
13
+ }
14
+
15
+ // src/runtime/index.ts
16
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
17
+
18
+ // src/runtime/credential-patterns.ts
19
+ var CREDENTIAL_PATTERNS = [
3
20
  // LLM provider keys
4
21
  {
5
22
  type: "anthropic_key",
6
23
  regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g
7
24
  },
8
- { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
9
- // GitHub
25
+ { type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?!ant)(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
26
+ // GitHub. `ghp_` is only the personal-access-token prefix — the OAuth
27
+ // (`gho_`), user-to-server (`ghu_`), server-to-server (`ghs_`) and
28
+ // refresh (`ghr_`) tokens grant the same or broader access and were
29
+ // previously not detected at all.
10
30
  { type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
31
+ {
32
+ type: "github_oauth_token",
33
+ regex: /(?<![A-Za-z0-9])gh[ousr]_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g
34
+ },
11
35
  { type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
36
+ // GitLab
37
+ { type: "gitlab_pat", regex: /(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
38
+ {
39
+ type: "gitlab_runner_token",
40
+ regex: /(?<![A-Za-z0-9])glrt-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
41
+ },
42
+ // npm — a leaked publish token is a supply-chain compromise.
43
+ { type: "npm_token", regex: /(?<![A-Za-z0-9])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g },
12
44
  // AWS
13
45
  { type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
14
46
  // GCP
15
47
  { type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
16
- // Slack
17
- { type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
48
+ // Slack. `xoxe` (token-rotation) and `xapp` (app-level) were missing;
49
+ // both are as sensitive as the bot/user tokens already covered.
50
+ {
51
+ type: "slack_token",
52
+ regex: /(?<![A-Za-z0-9-])xox[abposer]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g
53
+ },
54
+ { type: "slack_app_token", regex: /(?<![A-Za-z0-9-])xapp-\d-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
55
+ {
56
+ type: "slack_webhook",
57
+ regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Za-z0-9_-]+\/B[A-Za-z0-9_-]+\/[A-Za-z0-9]{16,}/g
58
+ },
18
59
  // Stripe
19
60
  {
20
61
  type: "stripe_key",
@@ -25,7 +66,7 @@ var BASE_PATTERNS = [
25
66
  // Telegram
26
67
  {
27
68
  type: "telegram_bot_token",
28
- regex: /\/bot\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
69
+ regex: /(?:(?<![A-Za-z0-9_])|(?<=(?:^|[^A-Za-z0-9_])bot))\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
29
70
  },
30
71
  // JWT
31
72
  {
@@ -35,35 +76,93 @@ var BASE_PATTERNS = [
35
76
  // Private keys
36
77
  {
37
78
  type: "private_key",
38
- regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----(?!\S)/g
79
+ regex: /(?:^|\n)(?:-----BEGIN (?:RSA|EC|OPENSSH|DSA)? ?PRIVATE KEY-----[\s\S]*?-----END (?:RSA|EC|OPENSSH|DSA)? ?PRIVATE KEY-----|-----BEGIN PGP PRIVATE KEY BLOCK-----[\s\S]*?-----END PGP PRIVATE KEY BLOCK-----)(?!\S)/g
39
80
  },
40
81
  // AI/ML provider tokens
41
82
  { type: "huggingface_token", regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g },
42
83
  { type: "replicate_token", regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
43
84
  { type: "perplexity_key", regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
44
85
  { type: "groq_key", regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
86
+ // SaaS / infrastructure tokens — each grants API access on the user's
87
+ // account, and each was previously invisible to this gate.
88
+ { type: "sendgrid_key", regex: /(?<![A-Za-z0-9])SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
89
+ { type: "digitalocean_token", regex: /(?<![A-Za-z0-9])dop_v1_[a-f0-9]{64}(?![A-Za-z0-9])/g },
90
+ { type: "doppler_token", regex: /(?<![A-Za-z0-9])dp\.(?:pt|st|sa|scim|audit)\.[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
91
+ { type: "shopify_token", regex: /(?<![A-Za-z0-9])shp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}(?![A-Za-z0-9])/g },
92
+ { type: "docker_pat", regex: /(?<![A-Za-z0-9])dckr_pat_[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
93
+ { type: "linear_key", regex: /(?<![A-Za-z0-9])lin_api_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
94
+ { type: "atlassian_token", regex: /(?<![A-Za-z0-9])ATATT3[A-Za-z0-9_\-=]{40,}(?![A-Za-z0-9_\-=])/g },
95
+ { type: "square_token", regex: /(?<![A-Za-z0-9])(?:sq0(?:atp|csp)-|EAAA)[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
96
+ {
97
+ type: "azure_storage_key",
98
+ regex: /AccountKey=[A-Za-z0-9+/]{80,}={0,2}/g
99
+ },
100
+ {
101
+ type: "google_oauth_client_secret",
102
+ regex: /(?<![A-Za-z0-9_-])GOCSPX-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
103
+ },
45
104
  // Bearer tokens
46
105
  {
47
106
  type: "bearer_token",
48
- regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?![A-Za-z0-9._~+/-])/g
107
+ regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?![A-Za-z0-9._~+/-])/g
108
+ },
109
+ // Database URIs. Require password-bearing user-info; credential-free values stay scannable.
110
+ { type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
111
+ {
112
+ type: "postgres_uri",
113
+ // Query parsers decode percent-encoded parameter names. Recognize each
114
+ // encoded character in `password` so mixed forms such as `pass%77ord`
115
+ // cannot bypass detection while keeping the scan strictly bounded.
116
+ regex: /postgres(?:ql)?:\/\/(?:[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+|[^&\s?"'`#]{1,2048}\?(?:(?!(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=)[^&\s#"'`]{1,256}&){0,32}(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=[^&\s#"'`]{1,4096})/g
49
117
  },
50
- // Database URIs
51
- { type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g },
52
- { type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
53
- { type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
54
- { type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g }
118
+ { type: "mysql_uri", regex: /mysql:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
119
+ { type: "redis_uri", regex: /redis:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g }
55
120
  ];
121
+ function cloneCredentialPatterns() {
122
+ return CREDENTIAL_PATTERNS.map((p) => ({
123
+ type: p.type,
124
+ regex: new RegExp(p.regex.source, p.regex.flags)
125
+ }));
126
+ }
127
+
128
+ // src/secret-scanner/index.ts
129
+ var BASE_PATTERNS = cloneCredentialPatterns();
56
130
  var PATTERNS = [...BASE_PATTERNS];
57
131
  var patternCacheKey = "";
132
+ var GROUP_INDEX_OF_PATTERN = [];
58
133
  var COMBINED_REGEX = buildCombinedRegex(PATTERNS);
59
- var RE_DOS_MAX_SCAN_LENGTH = 1e5;
134
+ var SCAN_WINDOW_LENGTH = 1e5;
135
+ var SCAN_WINDOW_OVERLAP = 4096;
136
+ var MAX_TOTAL_SCAN_LENGTH = 8 * 1024 * 1024;
137
+ var TOO_LARGE_MARKER = "unscannable_oversized_input";
60
138
  var RE_DOS_TIMEOUT_MS = 100;
139
+ function countCaptureGroups(source) {
140
+ try {
141
+ return new RegExp(`${source}|`).exec("").length - 1;
142
+ } catch {
143
+ return 0;
144
+ }
145
+ }
146
+ function patternTypeForGroups(groups) {
147
+ for (let i = 0; i < PATTERNS.length; i++) {
148
+ const at = GROUP_INDEX_OF_PATTERN[i];
149
+ if (at !== void 0 && groups[at] !== void 0) return PATTERNS[i].type;
150
+ }
151
+ return void 0;
152
+ }
61
153
  function buildCombinedRegex(patterns) {
62
- const newCacheKey = JSON.stringify(patterns.map((p) => p.type));
154
+ const newCacheKey = JSON.stringify(patterns.map((p) => [p.type, p.regex.source]));
63
155
  if (newCacheKey === patternCacheKey && COMBINED_REGEX) {
64
156
  return COMBINED_REGEX;
65
157
  }
66
158
  patternCacheKey = newCacheKey;
159
+ const offsets = [];
160
+ let cursor = 0;
161
+ for (const p of patterns) {
162
+ offsets.push(cursor);
163
+ cursor += 1 + countCaptureGroups(p.regex.source);
164
+ }
165
+ GROUP_INDEX_OF_PATTERN = offsets;
67
166
  return new RegExp(patterns.map((p) => `(${p.regex.source})`).join("|"), "g");
68
167
  }
69
168
  var state = {
@@ -81,29 +180,37 @@ var state = {
81
180
  /** PostToolUse hook handle so teardown can unregister. */
82
181
  postHookUnregister: null
83
182
  };
84
- function findMatches(text) {
85
- if (!text) return [];
86
- if (text.length > RE_DOS_MAX_SCAN_LENGTH) return [];
87
- const found = /* @__PURE__ */ new Set();
183
+ function scanWindow(window, found, startTime) {
88
184
  COMBINED_REGEX.lastIndex = 0;
89
185
  let m;
90
- const startTime = performance.now();
91
- while ((m = COMBINED_REGEX.exec(text)) !== null) {
186
+ while ((m = COMBINED_REGEX.exec(window)) !== null) {
92
187
  if (performance.now() - startTime > RE_DOS_TIMEOUT_MS) {
93
188
  throw new Error(
94
- `secret-scanner: ReDoS timeout \u2014 regex scan exceeded ${RE_DOS_TIMEOUT_MS}ms on ${text.length}-char input`
189
+ `secret-scanner: ReDoS timeout \u2014 regex scan exceeded ${RE_DOS_TIMEOUT_MS}ms on ${window.length}-char window`
95
190
  );
96
191
  }
97
- for (let i = 0; i < PATTERNS.length; i++) {
98
- if (m[i + 1] !== void 0) {
99
- found.add(PATTERNS[i].type);
100
- break;
101
- }
102
- }
192
+ const type = patternTypeForGroups(m.slice(1));
193
+ if (type !== void 0) found.add(type);
103
194
  if (m.index === COMBINED_REGEX.lastIndex) {
104
195
  COMBINED_REGEX.lastIndex += 1;
105
196
  }
106
197
  }
198
+ }
199
+ function findMatches(text) {
200
+ if (!text) return [];
201
+ const found = /* @__PURE__ */ new Set();
202
+ const startTime = performance.now();
203
+ if (text.length <= SCAN_WINDOW_LENGTH) {
204
+ scanWindow(text, found, startTime);
205
+ return Array.from(found).sort();
206
+ }
207
+ if (text.length > MAX_TOTAL_SCAN_LENGTH) {
208
+ return [TOO_LARGE_MARKER];
209
+ }
210
+ const stride = SCAN_WINDOW_LENGTH - SCAN_WINDOW_OVERLAP;
211
+ for (let offset = 0; offset < text.length; offset += stride) {
212
+ scanWindow(text.slice(offset, offset + SCAN_WINDOW_LENGTH), found, startTime);
213
+ }
107
214
  return Array.from(found).sort();
108
215
  }
109
216
  function scanInput(input, depth = 0) {
@@ -129,30 +236,55 @@ function scanInput(input, depth = 0) {
129
236
  }
130
237
  return null;
131
238
  }
239
+ function redactString(text) {
240
+ if (text.length > SCAN_WINDOW_LENGTH) {
241
+ return { ok: false, reason: "oversized_input" };
242
+ }
243
+ const startTime = performance.now();
244
+ let cursor = 0;
245
+ let output = "";
246
+ COMBINED_REGEX.lastIndex = 0;
247
+ let match;
248
+ while ((match = COMBINED_REGEX.exec(text)) !== null) {
249
+ if (performance.now() - startTime > RE_DOS_TIMEOUT_MS) {
250
+ return { ok: false, reason: "oversized_input" };
251
+ }
252
+ const type = patternTypeForGroups(match.slice(1));
253
+ output += text.slice(cursor, match.index);
254
+ output += `[REDACTED:${type ?? "unknown"}]`;
255
+ cursor = COMBINED_REGEX.lastIndex;
256
+ if (match.index === COMBINED_REGEX.lastIndex) {
257
+ COMBINED_REGEX.lastIndex += 1;
258
+ }
259
+ }
260
+ if (cursor === 0) return { ok: true, value: text };
261
+ return { ok: true, value: output + text.slice(cursor) };
262
+ }
132
263
  function redactInput(input, depth = 0) {
133
- if (input === null || input === void 0) return input;
134
- if (depth > 50) return input;
264
+ if (input === null || input === void 0) return { ok: true, value: input };
265
+ if (depth > 50) return { ok: false, reason: "maximum_depth_exceeded" };
135
266
  if (typeof input === "string") {
136
- return input.replace(COMBINED_REGEX, (_match, ...groups) => {
137
- for (let i = 0; i < PATTERNS.length; i++) {
138
- if (groups[i] !== void 0) {
139
- return `[REDACTED:${PATTERNS[i].type}]`;
140
- }
141
- }
142
- return "[REDACTED:unknown]";
143
- });
267
+ return redactString(input);
144
268
  }
145
269
  if (Array.isArray(input)) {
146
- return input.map((item) => redactInput(item, depth + 1));
270
+ const out = [];
271
+ for (const item of input) {
272
+ const redacted = redactInput(item, depth + 1);
273
+ if (!redacted.ok) return redacted;
274
+ out.push(redacted.value);
275
+ }
276
+ return { ok: true, value: out };
147
277
  }
148
278
  if (typeof input === "object") {
149
279
  const out = {};
150
280
  for (const [k, v] of Object.entries(input)) {
151
- out[k] = redactInput(v, depth + 1);
281
+ const redacted = redactInput(v, depth + 1);
282
+ if (!redacted.ok) return redacted;
283
+ out[k] = redacted.value;
152
284
  }
153
- return out;
285
+ return { ok: true, value: out };
154
286
  }
155
- return input;
287
+ return { ok: true, value: input };
156
288
  }
157
289
  var DEFAULTS = {
158
290
  matcher: "bash|write|edit",
@@ -212,20 +344,21 @@ function buildHook(cfg, log) {
212
344
  }
213
345
  if (cfg.mode === "redact") {
214
346
  const redacted = redactInput(input.toolInput);
215
- if (redacted !== null && typeof redacted === "object" && !Array.isArray(redacted)) {
347
+ if (redacted.ok && redacted.value !== null && typeof redacted.value === "object" && !Array.isArray(redacted.value)) {
216
348
  state.redactCount += 1;
217
349
  log.info(`[secret-scanner] redacted ${toolName} \u2014 matched: ${summary}`);
218
350
  return {
219
351
  decision: "allow",
220
- modifiedInput: redacted,
352
+ modifiedInput: redacted.value,
221
353
  additionalContext: `secret-scanner: redacted ${matched.length} credential pattern(s) from the ${toolName} arguments before execution.`
222
354
  };
223
355
  }
224
356
  state.blockCount += 1;
225
357
  state.lastBlock = { toolName, matchedTypes: matched, when };
358
+ const detail = !redacted.ok ? redacted.reason === "oversized_input" ? "an input field exceeds the safe scan limit" : "the input exceeds the safe nesting depth" : "the input has a non-object shape";
226
359
  return {
227
360
  decision: "block",
228
- reason: `secret-scanner: cannot safely redact '${toolName}' input (non-object shape); refusing to run.`
361
+ reason: `secret-scanner: cannot safely redact '${toolName}' because ${detail}; refusing to run.`
229
362
  };
230
363
  }
231
364
  state.allowCount += 1;
@@ -243,10 +376,21 @@ function buildPostHook(cfg, log) {
243
376
  const matched = findMatches(result.content);
244
377
  if (matched.length === 0) return;
245
378
  const toolName = input.toolName ?? "unknown";
246
- const summary = matched.join(", ");
379
+ const oversized = matched.includes(TOO_LARGE_MARKER);
380
+ const credentialMatches = matched.filter((type) => type !== TOO_LARGE_MARKER);
381
+ if (oversized && credentialMatches.length === 0) {
382
+ log.warn(
383
+ `[secret-scanner] POST-TOOL UNSCANNABLE: ${toolName} output exceeds ${MAX_TOTAL_SCAN_LENGTH} characters`
384
+ );
385
+ return {
386
+ additionalContext: `
387
+ \u26A0\uFE0F secret-scanner: the output of '${toolName}' exceeds the safe scan limit and could not be inspected for plaintext credentials. Do not treat this output as verified clean; avoid echoing, storing, committing, or transmitting it until it can be reviewed in smaller bounded sections.`
388
+ };
389
+ }
390
+ const summary = credentialMatches.join(", ");
247
391
  const when = (/* @__PURE__ */ new Date()).toISOString();
248
392
  state.leakCount += 1;
249
- state.lastLeak = { toolName, matchedTypes: matched, when };
393
+ state.lastLeak = { toolName, matchedTypes: credentialMatches, when };
250
394
  log.warn(`[secret-scanner] POST-TOOL LEAK: ${toolName} output matched ${summary}`);
251
395
  return {
252
396
  additionalContext: `
@@ -308,8 +452,8 @@ var plugin = {
308
452
  state.leakCount = 0;
309
453
  state.lastBlock = null;
310
454
  state.lastLeak = null;
311
- state.hookUnregister = null;
312
- state.postHookUnregister = null;
455
+ state.hookUnregister = releaseHandle(state.hookUnregister);
456
+ state.postHookUnregister = releaseHandle(state.postHookUnregister);
313
457
  const cfg = readConfig(api.config.extensions?.["secret-scanner"]);
314
458
  PATTERNS = [...BASE_PATTERNS];
315
459
  for (const cp of cfg.customPatterns) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security-hotspot-scanner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA6QrD,QAAA,MAAM,MAAM,EAAE,MAkQb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security-hotspot-scanner/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAkRrD,QAAA,MAAM,MAAM,EAAE,MAkQb,CAAC;eAEa,MAAM"}
@@ -4,6 +4,137 @@ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 }
4
4
 
5
5
  // src/runtime/index.ts
6
6
  import { basename, extname, isAbsolute, relative, resolve } from "node:path";
7
+
8
+ // src/runtime/local-bin.ts
9
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
10
+
11
+ // src/runtime/bounded-map.ts
12
+ var BoundedMap = class {
13
+ map = /* @__PURE__ */ new Map();
14
+ max;
15
+ ttlMs;
16
+ now;
17
+ /** Entries dropped to stay under `max`. Surfaced by plugin health(). */
18
+ evictions = 0;
19
+ constructor(options) {
20
+ const normalizedMax = Math.floor(options.max);
21
+ this.max = Number.isSafeInteger(normalizedMax) ? Math.max(1, normalizedMax) : 1;
22
+ this.ttlMs = options.ttlMs;
23
+ this.now = options.now ?? Date.now;
24
+ }
25
+ expired(entry) {
26
+ return this.ttlMs !== void 0 && this.now() - entry.storedAt > this.ttlMs;
27
+ }
28
+ get(key) {
29
+ const entry = this.map.get(key);
30
+ if (entry === void 0) return void 0;
31
+ if (this.expired(entry)) {
32
+ this.map.delete(key);
33
+ return void 0;
34
+ }
35
+ this.map.delete(key);
36
+ this.map.set(key, entry);
37
+ return entry.value;
38
+ }
39
+ /**
40
+ * Read without promoting the key to most-recently-used. Use for
41
+ * diagnostics that must not perturb the eviction order.
42
+ */
43
+ peek(key) {
44
+ const entry = this.map.get(key);
45
+ if (entry === void 0 || this.expired(entry)) return void 0;
46
+ return entry.value;
47
+ }
48
+ has(key) {
49
+ const entry = this.map.get(key);
50
+ if (entry === void 0) return false;
51
+ if (this.expired(entry)) {
52
+ this.map.delete(key);
53
+ return false;
54
+ }
55
+ return true;
56
+ }
57
+ set(key, value) {
58
+ this.map.delete(key);
59
+ this.map.set(key, { value, storedAt: this.now() });
60
+ while (this.map.size > this.max) {
61
+ const coldest = this.map.keys().next().value;
62
+ if (coldest === void 0) break;
63
+ this.map.delete(coldest);
64
+ this.evictions += 1;
65
+ }
66
+ return this;
67
+ }
68
+ delete(key) {
69
+ return this.map.delete(key);
70
+ }
71
+ clear() {
72
+ this.map.clear();
73
+ this.evictions = 0;
74
+ }
75
+ get size() {
76
+ return this.map.size;
77
+ }
78
+ /** How many entries have been dropped to respect `max`, since the last clear. */
79
+ get evictionCount() {
80
+ return this.evictions;
81
+ }
82
+ /** Drop every expired entry. Cheap enough to call from a status tool. */
83
+ prune() {
84
+ if (this.ttlMs === void 0) return 0;
85
+ let removed = 0;
86
+ for (const [key, entry] of this.map) {
87
+ if (this.expired(entry)) {
88
+ this.map.delete(key);
89
+ removed += 1;
90
+ }
91
+ }
92
+ return removed;
93
+ }
94
+ /** Live (non-expired) entries, coldest first. */
95
+ *entries() {
96
+ for (const [key, entry] of this.map) {
97
+ if (!this.expired(entry)) yield [key, entry.value];
98
+ }
99
+ }
100
+ [Symbol.iterator]() {
101
+ return this.entries();
102
+ }
103
+ };
104
+ var BoundedSet = class {
105
+ inner;
106
+ constructor(options) {
107
+ this.inner = new BoundedMap(options);
108
+ }
109
+ has(value) {
110
+ return this.inner.has(value);
111
+ }
112
+ add(value) {
113
+ this.inner.set(value, true);
114
+ return this;
115
+ }
116
+ delete(value) {
117
+ return this.inner.delete(value);
118
+ }
119
+ clear() {
120
+ this.inner.clear();
121
+ }
122
+ get size() {
123
+ return this.inner.size;
124
+ }
125
+ /** How many entries have been dropped to respect `max`, since the last clear. */
126
+ get evictionCount() {
127
+ return this.inner.evictionCount;
128
+ }
129
+ *values() {
130
+ for (const [key] of this.inner) yield key;
131
+ }
132
+ [Symbol.iterator]() {
133
+ return this.values();
134
+ }
135
+ };
136
+
137
+ // src/runtime/index.ts
7
138
  var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
8
139
  function hasLeadingDash(arg) {
9
140
  return arg.length > 0 && arg.startsWith("-");
@@ -30,7 +161,7 @@ var state = {
30
161
  skippedCount: 0,
31
162
  blockedCount: 0,
32
163
  lastResult: null,
33
- knownHotspots: /* @__PURE__ */ new Set(),
164
+ knownHotspots: new BoundedSet({ max: 5e3 }),
34
165
  hookUnregister: null
35
166
  };
36
167
  var DEFAULTS = {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/semantic-search-indexer/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA6brD,QAAA,MAAM,MAAM,EAAE,MA6Nb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/semantic-search-indexer/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAKH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAqcrD,QAAA,MAAM,MAAM,EAAE,MA6Nb,CAAC;eAEa,MAAM"}
@@ -1,7 +1,9 @@
1
1
  // src/semantic-search-indexer/index.ts
2
2
  import * as fs from "node:fs/promises";
3
3
  import { isAbsolute, relative, resolve } from "node:path";
4
+ import { DEFAULT_WALK_IGNORE_DIRS } from "@wrongstack/core/utils";
4
5
  var API_VERSION = "^0.1.10";
6
+ var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
7
  var state = {
6
8
  index: null,
7
9
  cachedPath: null,
@@ -45,7 +47,11 @@ var DEFAULTS = {
45
47
  ".scss",
46
48
  ".html"
47
49
  ],
48
- excludePatterns: ["node_modules", "\\.git", "\\.wrongstack", "\\.temp_files", "coverage", "dist"],
50
+ excludePatterns: [
51
+ ...DEFAULT_WALK_IGNORE_DIRS.map(escapeRegex),
52
+ "\\.wrongstack",
53
+ "\\.temp_files"
54
+ ],
49
55
  maxFileBytes: 1e6,
50
56
  defaultLimit: 10,
51
57
  minTokenLength: 2,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/semver-bump/index.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAuCrD,KAAK,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAqGD;+DAC+D;AAC/D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAQnF;AAmBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,QAAQ,CAIrE;AAmED,QAAA,MAAM,MAAM,EAAE,MAgcb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/semver-bump/index.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAuCrD,KAAK,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAqGD;+DAC+D;AAC/D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAQnF;AAmBD,wBAAgB,aAAa,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,QAAQ,CAIrE;AAmED,QAAA,MAAM,MAAM,EAAE,MA0db,CAAC;eAEa,MAAM"}
@@ -293,10 +293,29 @@ var plugin = {
293
293
  }
294
294
  }
295
295
  } else {
296
+ const pending = [];
296
297
  for (const manifest of changed) {
297
- const pkgData = JSON.parse(await readFile(manifest, "utf-8"));
298
+ let pkgData;
299
+ try {
300
+ pkgData = JSON.parse(await readFile(manifest, "utf-8"));
301
+ } catch (err) {
302
+ return {
303
+ ok: false,
304
+ error: `cannot bump: ${manifest} is not readable as JSON (${toErrorMessage(err)}). No manifests were modified.`
305
+ };
306
+ }
307
+ if (!pkgData || typeof pkgData !== "object") {
308
+ return {
309
+ ok: false,
310
+ error: `cannot bump: ${manifest} does not contain a JSON object. No manifests were modified.`
311
+ };
312
+ }
298
313
  pkgData.version = newVersion;
299
- await writeFile(manifest, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
314
+ pending.push({ path: manifest, contents: `${JSON.stringify(pkgData, null, 2)}
315
+ ` });
316
+ }
317
+ for (const { path, contents } of pending) {
318
+ await writeFile(path, contents, "utf-8");
300
319
  }
301
320
  }
302
321
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session-recap/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA4MrD,QAAA,MAAM,MAAM,EAAE,MA4Yb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session-recap/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA6MrD,QAAA,MAAM,MAAM,EAAE,MA4Yb,CAAC;eAEa,MAAM"}
@@ -1,3 +1,20 @@
1
+ // src/runtime/local-bin.ts
2
+ import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
3
+
4
+ // src/runtime/handles.ts
5
+ function releaseHandle(off) {
6
+ if (off) {
7
+ try {
8
+ off();
9
+ } catch {
10
+ }
11
+ }
12
+ return null;
13
+ }
14
+
15
+ // src/runtime/index.ts
16
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
17
+
1
18
  // src/session-recap/index.ts
2
19
  var state = {
3
20
  recapsPublished: 0,
@@ -155,7 +172,7 @@ var plugin = {
155
172
  state.commitCount = 0;
156
173
  state.startedAt = null;
157
174
  state.lastActivityAt = null;
158
- state.stopHookUnregister = null;
175
+ state.stopHookUnregister = releaseHandle(state.stopHookUnregister);
159
176
  for (const off of state.eventUnsubscribers) {
160
177
  try {
161
178
  off();
@@ -19,6 +19,8 @@
19
19
  * @public
20
20
  */
21
21
  import type { Plugin } from '@wrongstack/core/types';
22
+ /** True when `name` is something this tool may safely substitute. */
23
+ export declare function isIdentifier(name: string): boolean;
22
24
  declare const plugin: Plugin;
23
25
  export default plugin;
24
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/smart-rename/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AA0FrD,QAAA,MAAM,MAAM,EAAE,MAqIb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/smart-rename/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAkFrD,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAmCD,QAAA,MAAM,MAAM,EAAE,MAgJb,CAAC;eAEa,MAAM"}
@@ -38,8 +38,15 @@ function relativePath(p) {
38
38
  function escapeRegex(s) {
39
39
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
40
40
  }
41
+ var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
42
+ function isIdentifier(name) {
43
+ return IDENTIFIER_RE.test(name);
44
+ }
41
45
  function renameInContent(content, oldName, newName) {
42
- const re = new RegExp(`\\b${escapeRegex(oldName)}\\b`, "g");
46
+ const re = new RegExp(
47
+ `(?<![A-Za-z0-9_$])${escapeRegex(oldName)}(?![A-Za-z0-9_$])`,
48
+ "g"
49
+ );
43
50
  let replacements = 0;
44
51
  const preview = content.replace(re, () => {
45
52
  replacements++;
@@ -101,6 +108,12 @@ var plugin = {
101
108
  if (!newName || typeof newName !== "string" || newName.length === 0) {
102
109
  return { ok: false, error: "newName is required" };
103
110
  }
111
+ if (!isIdentifier(oldName)) {
112
+ return { ok: false, error: `oldName "${oldName}" is not a valid identifier` };
113
+ }
114
+ if (!isIdentifier(newName)) {
115
+ return { ok: false, error: `newName "${newName}" is not a valid identifier` };
116
+ }
104
117
  if (!withinProject(rawPath)) {
105
118
  return { ok: false, error: "path is outside the project root" };
106
119
  }
@@ -166,5 +179,6 @@ var plugin = {
166
179
  };
167
180
  var smart_rename_default = plugin;
168
181
  export {
169
- smart_rename_default as default
182
+ smart_rename_default as default,
183
+ isIdentifier
170
184
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/spec-linker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAwPrD,QAAA,MAAM,MAAM,EAAE,MAuOb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/spec-linker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAyPrD,QAAA,MAAM,MAAM,EAAE,MAuOb,CAAC;eAEa,MAAM"}