@wrongstack/plugins 0.307.1 → 0.308.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.
- package/dist/accessibility-auditor/index.d.ts +5 -0
- package/dist/accessibility-auditor.js +67 -11
- package/dist/audit/index.d.ts +6 -6
- package/dist/audit.js +7 -7
- package/dist/code-metrics/index.d.ts +9 -0
- package/dist/code-metrics.js +42 -11
- package/dist/dep-guard.js +4 -2
- package/dist/dependency-vulnerability-gate/index.d.ts +4 -0
- package/dist/dependency-vulnerability-gate.js +435 -62
- package/dist/factories/index.d.ts +1 -1
- package/dist/factories.js +1 -1
- package/dist/file-watcher.js +25 -10
- package/dist/format-on-save.js +10 -4
- package/dist/import-organizer/index.d.ts +4 -0
- package/dist/import-organizer.js +26 -1
- package/dist/index.js +719 -234
- package/dist/license-audit-gate/index.d.ts +1 -0
- package/dist/license-audit-gate.js +445 -83
- package/dist/lint-gate/index.d.ts +13 -0
- package/dist/lint-gate.js +13 -10
- package/dist/llm-cache.js +6 -0
- package/dist/manifest.js +1 -1
- package/dist/path-guard/glob.d.ts +23 -0
- package/dist/path-guard/index.d.ts +2 -0
- package/dist/path-guard.js +214 -32
- package/dist/plugin-audit-catalog.js +7 -7
- package/dist/prompt-firewall/index.d.ts +49 -40
- package/dist/prompt-firewall.js +302 -29
- package/dist/runtime/h1-state.d.ts +62 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/redos-guard.d.ts +69 -0
- package/dist/runtime/sandbox.d.ts +59 -0
- package/dist/runtime.js +210 -19
- package/dist/secret-scanner.js +16 -2
- package/dist/spec-linker.js +1 -1
- package/dist/type-gate.js +33 -3
- package/package.json +7 -7
|
@@ -32,6 +32,11 @@ export interface A11yFinding {
|
|
|
32
32
|
rule: A11yRule;
|
|
33
33
|
severity: 'error' | 'warning';
|
|
34
34
|
message: string;
|
|
35
|
+
/**
|
|
36
|
+
* Optional scan limitation. Cross-file labels (e.g. `<Label>` in a
|
|
37
|
+
* sibling component) are invisible to this single-file regex walk.
|
|
38
|
+
*/
|
|
39
|
+
note?: string;
|
|
35
40
|
}
|
|
36
41
|
declare const plugin: Plugin;
|
|
37
42
|
export default plugin;
|
|
@@ -115,15 +115,40 @@ var INPUT_BUTTON = /<input\b[^>]*\btype\s*=\s*["'](submit|button|reset)["'][^>]*
|
|
|
115
115
|
var ATTR_ID = /\bid\s*=\s*["']([^"']+)["']/gi;
|
|
116
116
|
var ATTR_ALT = /\balt\s*=/i;
|
|
117
117
|
var ATTR_ARIA_LABEL = /\b(?:aria-label|aria-labelledby)\s*=/i;
|
|
118
|
+
var ATTR_ARIA_DESCRIBEDBY = /\baria-describedby\s*=/i;
|
|
118
119
|
var ATTR_TITLE = /\btitle\s*=/i;
|
|
119
120
|
var ATTR_PLACEHOLDER = /\bplaceholder\s*=/i;
|
|
120
121
|
var ATTR_VALUE = /\bvalue\s*=/i;
|
|
122
|
+
var ATTR_ROLE_DECORATIVE = /\brole\s*=\s*["'](?:presentation|none)["']/i;
|
|
123
|
+
var SINGLE_FILE_LABEL_NOTE = "Single-file heuristic: a label declared in a sibling component file is not visible to this scan.";
|
|
124
|
+
function escapeRegExp(value) {
|
|
125
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
126
|
+
}
|
|
121
127
|
function hasMeaningfulAlt(tag) {
|
|
122
128
|
const m = ATTR_ALT.exec(tag);
|
|
123
129
|
ATTR_ALT.lastIndex = 0;
|
|
124
130
|
if (!m) return false;
|
|
125
131
|
const valMatch = tag.match(/\balt\s*=\s*["']?([^"'\s>]*)["']?/i);
|
|
126
|
-
|
|
132
|
+
const alt = valMatch ? valMatch[1].trim() : "";
|
|
133
|
+
if (alt.length > 0) return true;
|
|
134
|
+
return ATTR_ROLE_DECORATIVE.test(tag);
|
|
135
|
+
}
|
|
136
|
+
function hasFieldsetLegendLabel(tag, content) {
|
|
137
|
+
const labelledBy = tag.match(/\baria-labelledby\s*=\s*["']([^"']+)["']/i);
|
|
138
|
+
if (labelledBy?.[1]) {
|
|
139
|
+
for (const id of labelledBy[1].split(/\s+/).filter(Boolean)) {
|
|
140
|
+
const idRe = new RegExp(`\\bid\\s*=\\s*["']${escapeRegExp(id)}["']`, "i");
|
|
141
|
+
if (idRe.test(content)) return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const typeMatch = tag.match(/\btype\s*=\s*["']?([^"'\s>]*)["']?/i);
|
|
145
|
+
const type = typeMatch ? typeMatch[1].toLowerCase() : "text";
|
|
146
|
+
if (type === "checkbox" || type === "radio") {
|
|
147
|
+
return /<fieldset\b[\s\S]*?<legend\b[\s\S]*?<\/legend>[\s\S]*?<input\b[\s\S]*?<\/fieldset>/i.test(
|
|
148
|
+
content
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
127
152
|
}
|
|
128
153
|
async function auditFile(filePath, projectRoot) {
|
|
129
154
|
let content;
|
|
@@ -135,13 +160,14 @@ async function auditFile(filePath, projectRoot) {
|
|
|
135
160
|
const findings = [];
|
|
136
161
|
const lines = content.split(/\r?\n/);
|
|
137
162
|
const idsByValue = /* @__PURE__ */ new Map();
|
|
138
|
-
function add(line, rule, severity, message) {
|
|
163
|
+
function add(line, rule, severity, message, note) {
|
|
139
164
|
findings.push({
|
|
140
165
|
file: relative2(projectRoot, filePath),
|
|
141
166
|
line,
|
|
142
167
|
rule,
|
|
143
168
|
severity,
|
|
144
|
-
message
|
|
169
|
+
message,
|
|
170
|
+
...note ? { note } : {}
|
|
145
171
|
});
|
|
146
172
|
}
|
|
147
173
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -169,17 +195,36 @@ async function auditFile(filePath, projectRoot) {
|
|
|
169
195
|
continue;
|
|
170
196
|
}
|
|
171
197
|
const hasAriaLabel = ATTR_ARIA_LABEL.test(tag);
|
|
198
|
+
const hasDescribedBy = ATTR_ARIA_DESCRIBEDBY.test(tag);
|
|
172
199
|
const hasTitle = ATTR_TITLE.test(tag);
|
|
173
200
|
const idMatchLocal = tag.match(/\bid\s*=\s*["']([^"']+)["']/i);
|
|
174
201
|
const id = idMatchLocal ? idMatchLocal[1] : null;
|
|
175
202
|
let hasLabelFor = false;
|
|
176
203
|
if (id) {
|
|
177
|
-
const labelForRe = new RegExp(
|
|
204
|
+
const labelForRe = new RegExp(
|
|
205
|
+
`<label\\b[^>]*\\bfor\\s*=\\s*["']${escapeRegExp(id)}["']`,
|
|
206
|
+
"i"
|
|
207
|
+
);
|
|
178
208
|
hasLabelFor = labelForRe.test(content);
|
|
179
209
|
}
|
|
180
210
|
const wrappedInLabel = /<label\b[\s\S]*?<input\b[\s\S]*?<\/label>/i.test(content);
|
|
181
|
-
|
|
182
|
-
|
|
211
|
+
const hasLegend = hasFieldsetLegendLabel(tag, content);
|
|
212
|
+
const hasPrimaryLabel = hasAriaLabel || hasTitle || hasLabelFor || wrappedInLabel || hasLegend;
|
|
213
|
+
if (!hasPrimaryLabel && hasDescribedBy) {
|
|
214
|
+
add(
|
|
215
|
+
lineNo,
|
|
216
|
+
"low-contrast-placeholder",
|
|
217
|
+
"warning",
|
|
218
|
+
`<input type="${type}"> uses aria-describedby as a description (supplementary, not a primary label)`
|
|
219
|
+
);
|
|
220
|
+
} else if (!hasPrimaryLabel) {
|
|
221
|
+
add(
|
|
222
|
+
lineNo,
|
|
223
|
+
"missing-input-label",
|
|
224
|
+
"error",
|
|
225
|
+
`<input type="${type}"> is missing an associated label`,
|
|
226
|
+
SINGLE_FILE_LABEL_NOTE
|
|
227
|
+
);
|
|
183
228
|
}
|
|
184
229
|
if (ATTR_PLACEHOLDER.test(tag)) {
|
|
185
230
|
add(lineNo, "low-contrast-placeholder", "warning", "<input> uses placeholder text (often low contrast and disappears on input)");
|
|
@@ -239,15 +284,24 @@ async function auditPath(rawPath, cfg) {
|
|
|
239
284
|
truncated
|
|
240
285
|
};
|
|
241
286
|
}
|
|
287
|
+
function truncationWarning(result) {
|
|
288
|
+
if (!result.truncated) return "";
|
|
289
|
+
const unexamined = Math.max(0, result.fileCount - (result.scannedFiles ?? result.fileCount));
|
|
290
|
+
return `partial scan \u2014 ${unexamined} files not examined`;
|
|
291
|
+
}
|
|
242
292
|
function formatSummary(result) {
|
|
293
|
+
const trunc = truncationWarning(result);
|
|
243
294
|
if (result.findings.length === 0) {
|
|
244
|
-
|
|
295
|
+
const clean = `
|
|
245
296
|
\u2705 accessibility-auditor: no issues found in ${result.path} (${result.fileCount} file${result.fileCount === 1 ? "" : "s"}).`;
|
|
297
|
+
return trunc ? `${clean}
|
|
298
|
+
\u26A0\uFE0F ${trunc}` : clean;
|
|
246
299
|
}
|
|
247
300
|
const lines = result.findings.map((f) => ` - ${f.file}:${f.line} \u2014 ${f.message} (${f.rule})`);
|
|
248
301
|
return `
|
|
249
302
|
\u26A0\uFE0F accessibility-auditor: ${result.findings.length} issue(s) in ${result.path} (${result.fileCount} file${result.fileCount === 1 ? "" : "s"}):
|
|
250
|
-
` + lines.join("\n") + "\nConsider adding missing labels/alt text or resolving duplicate ids."
|
|
303
|
+
` + lines.join("\n") + "\nConsider adding missing labels/alt text or resolving duplicate ids." + (trunc ? `
|
|
304
|
+
\u26A0\uFE0F ${trunc}` : "");
|
|
251
305
|
}
|
|
252
306
|
var plugin = {
|
|
253
307
|
name: "accessibility-auditor",
|
|
@@ -318,9 +372,9 @@ var plugin = {
|
|
|
318
372
|
findingCount: result.findings.length,
|
|
319
373
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
320
374
|
};
|
|
321
|
-
if (result.findings.length === 0) return;
|
|
375
|
+
if (result.findings.length === 0 && !result.truncated) return;
|
|
322
376
|
const summary = formatSummary(result);
|
|
323
|
-
if (cfg.severity === "block") {
|
|
377
|
+
if (cfg.severity === "block" && result.findings.length > 0) {
|
|
324
378
|
return { decision: "block", reason: summary };
|
|
325
379
|
}
|
|
326
380
|
return { additionalContext: summary };
|
|
@@ -361,6 +415,7 @@ var plugin = {
|
|
|
361
415
|
findingCount: result.findings.length,
|
|
362
416
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
363
417
|
};
|
|
418
|
+
const warning = truncationWarning(result);
|
|
364
419
|
return {
|
|
365
420
|
ok: true,
|
|
366
421
|
path: result.path,
|
|
@@ -370,7 +425,8 @@ var plugin = {
|
|
|
370
425
|
// that reports few findings must not read as a clean result.
|
|
371
426
|
truncated: result.truncated,
|
|
372
427
|
findingCount: result.findings.length,
|
|
373
|
-
findings: result.findings
|
|
428
|
+
findings: result.findings,
|
|
429
|
+
...warning ? { additionalContext: `\u26A0\uFE0F ${warning}`, warning } : {}
|
|
374
430
|
};
|
|
375
431
|
}
|
|
376
432
|
});
|
package/dist/audit/index.d.ts
CHANGED
|
@@ -208,6 +208,12 @@ export declare const OFFICIAL_PLUGIN_AUDIT_ENTRIES: readonly [{
|
|
|
208
208
|
readonly summary: 'Scans tool output (fetched pages, files) for prompt-injection patterns and warns the model that content is data, not instructions';
|
|
209
209
|
readonly defaultState: 'active';
|
|
210
210
|
readonly canDisable: true;
|
|
211
|
+
}, {
|
|
212
|
+
readonly name: 'prompt-firewall';
|
|
213
|
+
readonly risk: 'high';
|
|
214
|
+
readonly summary: 'Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.';
|
|
215
|
+
readonly defaultState: 'inactive';
|
|
216
|
+
readonly canDisable: true;
|
|
211
217
|
}, {
|
|
212
218
|
readonly name: 'llm-cache';
|
|
213
219
|
readonly risk: 'medium';
|
|
@@ -226,12 +232,6 @@ export declare const OFFICIAL_PLUGIN_AUDIT_ENTRIES: readonly [{
|
|
|
226
232
|
readonly summary: 'Collects session work (commits, edited files, diff) and drafts a pull-request description';
|
|
227
233
|
readonly defaultState: 'inactive';
|
|
228
234
|
readonly canDisable: true;
|
|
229
|
-
}, {
|
|
230
|
-
readonly name: 'prompt-firewall';
|
|
231
|
-
readonly risk: 'high';
|
|
232
|
-
readonly summary: 'Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.';
|
|
233
|
-
readonly defaultState: 'inactive';
|
|
234
|
-
readonly canDisable: true;
|
|
235
235
|
}, {
|
|
236
236
|
readonly name: 'auto-escalate';
|
|
237
237
|
readonly risk: 'medium';
|
package/dist/audit.js
CHANGED
|
@@ -245,6 +245,13 @@ var OFFICIAL_PLUGIN_AUDIT_ENTRIES = [
|
|
|
245
245
|
defaultState: "active",
|
|
246
246
|
canDisable: true
|
|
247
247
|
},
|
|
248
|
+
{
|
|
249
|
+
name: "prompt-firewall",
|
|
250
|
+
risk: "high",
|
|
251
|
+
summary: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.",
|
|
252
|
+
defaultState: "inactive",
|
|
253
|
+
canDisable: true
|
|
254
|
+
},
|
|
248
255
|
{
|
|
249
256
|
name: "llm-cache",
|
|
250
257
|
risk: "medium",
|
|
@@ -266,13 +273,6 @@ var OFFICIAL_PLUGIN_AUDIT_ENTRIES = [
|
|
|
266
273
|
defaultState: "inactive",
|
|
267
274
|
canDisable: true
|
|
268
275
|
},
|
|
269
|
-
{
|
|
270
|
-
name: "prompt-firewall",
|
|
271
|
-
risk: "high",
|
|
272
|
-
summary: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.",
|
|
273
|
-
defaultState: "inactive",
|
|
274
|
-
canDisable: true
|
|
275
|
-
},
|
|
276
276
|
{
|
|
277
277
|
name: "auto-escalate",
|
|
278
278
|
risk: "medium",
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
* @public
|
|
25
25
|
*/
|
|
26
26
|
import type { Plugin } from '@wrongstack/core/types';
|
|
27
|
+
/**
|
|
28
|
+
* Heuristic cyclomatic-complexity formula (per file, not per function):
|
|
29
|
+
* control keywords (if | else if | for | while | switch | catch)
|
|
30
|
+
* + short-circuit / nullish operators (&& || ??)
|
|
31
|
+
* + assignment-or operators (||= &&= ??=)
|
|
32
|
+
* + ternary `?`
|
|
33
|
+
* Optional chaining (`?.`) is not a decision point — `a?.b ?? c` adds 1.
|
|
34
|
+
*/
|
|
35
|
+
export declare const COMPLEXITY_FORMULA = "control(if|else if|for|while|switch|catch) + (&& || ?? ||= &&= ??=) + ternary ?; optional chaining ?. is not counted";
|
|
27
36
|
declare const plugin: Plugin;
|
|
28
37
|
export default plugin;
|
|
29
38
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/code-metrics.js
CHANGED
|
@@ -115,36 +115,65 @@ function countFunctions(content) {
|
|
|
115
115
|
for (const _match of content.matchAll(arrowRe)) count++;
|
|
116
116
|
return count;
|
|
117
117
|
}
|
|
118
|
+
var COMPLEXITY_FORMULA = "control(if|else if|for|while|switch|catch) + (&& || ?? ||= &&= ??=) + ternary ?; optional chaining ?. is not counted";
|
|
118
119
|
function countComplexity(content) {
|
|
119
120
|
const controlRe = /\b(if|else\s+if|for|while|switch|catch)\b/g;
|
|
120
|
-
const operatorRe = /[?&|]/g;
|
|
121
121
|
let complexity = 0;
|
|
122
122
|
controlRe.lastIndex = 0;
|
|
123
123
|
for (const _match of content.matchAll(controlRe)) complexity++;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
124
|
+
for (let i = 0; i < content.length; i++) {
|
|
125
|
+
const c = content[i];
|
|
126
|
+
const n1 = content[i + 1];
|
|
127
|
+
const n2 = content[i + 2];
|
|
128
|
+
if (c === "?" && n1 === "?" && n2 === "=") {
|
|
128
129
|
complexity++;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
i += 2;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (c === "|" && n1 === "|" && n2 === "=" || c === "&" && n1 === "&" && n2 === "=") {
|
|
134
|
+
complexity++;
|
|
135
|
+
i += 2;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (c === "?" && n1 === "?" || c === "|" && n1 === "|" || c === "&" && n1 === "&") {
|
|
139
|
+
complexity++;
|
|
140
|
+
i += 1;
|
|
141
|
+
continue;
|
|
132
142
|
}
|
|
143
|
+
if (c === "?" && n1 === ".") {
|
|
144
|
+
i += 1;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (c === "?") complexity++;
|
|
133
148
|
}
|
|
134
149
|
return complexity;
|
|
135
150
|
}
|
|
136
151
|
function analyzeFile(filePath, content) {
|
|
152
|
+
if (content.length === 0) {
|
|
153
|
+
return {
|
|
154
|
+
file: relativePath(filePath),
|
|
155
|
+
lines: 0,
|
|
156
|
+
codeLines: 0,
|
|
157
|
+
commentLines: 0,
|
|
158
|
+
blankLines: 0,
|
|
159
|
+
functionCount: 0,
|
|
160
|
+
complexity: 0
|
|
161
|
+
};
|
|
162
|
+
}
|
|
137
163
|
const lines = content.split(/\r?\n/);
|
|
138
164
|
let codeLines = 0;
|
|
139
165
|
let commentLines = 0;
|
|
140
166
|
let blankLines = 0;
|
|
141
167
|
let inBlockComment = false;
|
|
142
|
-
for (
|
|
143
|
-
const line =
|
|
168
|
+
for (let i = 0; i < lines.length; i++) {
|
|
169
|
+
const line = lines[i].trim();
|
|
144
170
|
if (line.length === 0) {
|
|
145
171
|
blankLines++;
|
|
146
172
|
continue;
|
|
147
173
|
}
|
|
174
|
+
if (i === 0 && line.startsWith("#!")) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
148
177
|
if (inBlockComment) {
|
|
149
178
|
commentLines++;
|
|
150
179
|
if (line.includes("*/")) inBlockComment = false;
|
|
@@ -305,7 +334,8 @@ var plugin = {
|
|
|
305
334
|
files: state.fileCount,
|
|
306
335
|
hookInvocations: state.hookInvocationCount,
|
|
307
336
|
errors: state.errorCount
|
|
308
|
-
}
|
|
337
|
+
},
|
|
338
|
+
complexityFormula: COMPLEXITY_FORMULA
|
|
309
339
|
};
|
|
310
340
|
}
|
|
311
341
|
});
|
|
@@ -350,5 +380,6 @@ var plugin = {
|
|
|
350
380
|
};
|
|
351
381
|
var code_metrics_default = plugin;
|
|
352
382
|
export {
|
|
383
|
+
COMPLEXITY_FORMULA,
|
|
353
384
|
code_metrics_default as default
|
|
354
385
|
};
|
package/dist/dep-guard.js
CHANGED
|
@@ -48,10 +48,12 @@ function parseInstallCommands(command) {
|
|
|
48
48
|
if (!cleaned) continue;
|
|
49
49
|
let name = cleaned;
|
|
50
50
|
let version = null;
|
|
51
|
-
const pipMatch = /^([A-Za-z0-9_.-]+)\s*(
|
|
51
|
+
const pipMatch = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(==|>=|<=|~=|!=|>|<)\s*(.+)$/.exec(cleaned);
|
|
52
52
|
if (pipMatch?.[1]) {
|
|
53
53
|
name = pipMatch[1];
|
|
54
|
-
|
|
54
|
+
const op = pipMatch[2] ?? "";
|
|
55
|
+
const ver = (pipMatch[3] ?? "").trim();
|
|
56
|
+
version = op === "==" || op === "" ? ver || null : `${op}${ver}`;
|
|
55
57
|
} else {
|
|
56
58
|
const at = cleaned.lastIndexOf("@");
|
|
57
59
|
if (at > 0) {
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
* @public
|
|
29
29
|
*/
|
|
30
30
|
import type { Plugin } from '@wrongstack/core/types';
|
|
31
|
+
export declare function isInstallCommand(input: {
|
|
32
|
+
toolName?: string | undefined;
|
|
33
|
+
toolInput?: unknown;
|
|
34
|
+
}): boolean;
|
|
31
35
|
declare const plugin: Plugin;
|
|
32
36
|
export default plugin;
|
|
33
37
|
//# sourceMappingURL=index.d.ts.map
|