@wrongstack/plugins 0.308.6 → 0.309.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/dist/accessibility-auditor.js +26 -82
- package/dist/agent-handoff.js +16 -26
- package/dist/auto-i18n-extractor.js +19 -23
- package/dist/branch-guard.js +19 -111
- package/dist/changelog-writer.js +20 -133
- package/dist/code-metrics.js +26 -71
- package/dist/commit-validator.js +16 -14
- package/dist/config-validator.js +18 -22
- package/dist/cost-tracker.js +15 -96
- package/dist/dead-code-detector.js +27 -31
- package/dist/dependency-vulnerability-gate.js +18 -15
- package/dist/diff-summary.js +19 -128
- package/dist/doc-sync-guard.js +24 -39
- package/dist/duplicate-code-detector.js +30 -169
- package/dist/feature-flag-tracker.js +26 -71
- package/dist/file-watcher.js +19 -23
- package/dist/format-on-save.js +25 -214
- package/dist/import-organizer.js +31 -106
- package/dist/index.js +452 -1236
- package/dist/interface-contract-guard.js +26 -71
- package/dist/lint-gate.js +19 -111
- package/dist/migration-planner.js +28 -120
- package/dist/notify-hub.js +18 -28
- package/dist/path-guard.js +27 -102
- package/dist/pr-drafter.js +18 -16
- package/dist/prompt-firewall.js +8 -205
- package/dist/refactor-suggester.js +27 -166
- package/dist/release-notes-generator.js +6 -35
- package/dist/runtime/bounded-map.d.ts +2 -85
- package/dist/runtime/credential-patterns.d.ts +2 -41
- package/dist/runtime/h1-state.d.ts +2 -61
- package/dist/runtime/handles.d.ts +2 -45
- package/dist/runtime/index.d.ts +8 -180
- package/dist/runtime/llm.d.ts +2 -43
- package/dist/runtime/local-bin.d.ts +2 -119
- package/dist/runtime/redos-guard.d.ts +2 -68
- package/dist/runtime/safe-json.d.ts +2 -24
- package/dist/runtime/sandbox.d.ts +2 -58
- package/dist/runtime.js +1 -868
- package/dist/schema-evolution-guard.js +20 -24
- package/dist/secret-scanner.js +22 -146
- package/dist/security-hotspot-scanner.js +24 -154
- package/dist/session-recap.js +16 -14
- package/dist/spec-linker.js +19 -17
- package/dist/template-engine.js +22 -37
- package/dist/test-coverage-gate.js +19 -34
- package/dist/test-generator.js +6 -35
- package/dist/test-runner-gate.js +34 -143
- package/dist/todo-listener.js +17 -38
- package/dist/type-gate.js +23 -311
- package/package.json +4 -3
package/dist/path-guard.js
CHANGED
|
@@ -1,79 +1,22 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __copyProps = (to, from, except, desc) => {
|
|
6
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
7
|
+
for (let key of __getOwnPropNames(from))
|
|
8
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
9
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
10
|
+
}
|
|
11
|
+
return to;
|
|
12
|
+
};
|
|
13
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
14
|
+
|
|
1
15
|
// src/runtime/redos-guard.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const workerSource = buildWorkerSource(re.source, input, re.flags);
|
|
7
|
-
const worker = new Worker(workerSource, {
|
|
8
|
-
eval: true,
|
|
9
|
-
name: `redos-guard:${re.source.slice(0, 32)}`
|
|
10
|
-
});
|
|
11
|
-
return new Promise((resolve3) => {
|
|
12
|
-
let settled = false;
|
|
13
|
-
const onMessage = (msg) => {
|
|
14
|
-
if (settled) return;
|
|
15
|
-
settled = true;
|
|
16
|
-
clearTimeout(timer);
|
|
17
|
-
worker.terminate().catch(() => {
|
|
18
|
-
});
|
|
19
|
-
if (!msg.ok) {
|
|
20
|
-
resolve3({ timedOut: true, match: null });
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
resolve3({ timedOut: false, match: msg.match });
|
|
24
|
-
};
|
|
25
|
-
const onError = () => {
|
|
26
|
-
if (settled) return;
|
|
27
|
-
settled = true;
|
|
28
|
-
clearTimeout(timer);
|
|
29
|
-
worker.terminate().catch(() => {
|
|
30
|
-
});
|
|
31
|
-
resolve3({ timedOut: true, match: null });
|
|
32
|
-
};
|
|
33
|
-
const timer = setTimeout(() => {
|
|
34
|
-
if (settled) return;
|
|
35
|
-
settled = true;
|
|
36
|
-
const elapsedMs = Date.now() - start;
|
|
37
|
-
worker.terminate().catch(() => {
|
|
38
|
-
});
|
|
39
|
-
try {
|
|
40
|
-
opts.onTimeout?.({
|
|
41
|
-
regex: re,
|
|
42
|
-
input,
|
|
43
|
-
budgetMs: opts.budgetMs,
|
|
44
|
-
elapsedMs
|
|
45
|
-
});
|
|
46
|
-
} catch {
|
|
47
|
-
}
|
|
48
|
-
resolve3({ timedOut: true, match: null });
|
|
49
|
-
}, opts.budgetMs);
|
|
50
|
-
timer.unref?.();
|
|
51
|
-
worker.on("message", onMessage);
|
|
52
|
-
worker.on("error", onError);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function buildWorkerSource(source, input, flags) {
|
|
56
|
-
const S = JSON.stringify(source);
|
|
57
|
-
const I = JSON.stringify(input);
|
|
58
|
-
const F = JSON.stringify(flags);
|
|
59
|
-
return `
|
|
60
|
-
const { parentPort } = require('node:worker_threads');
|
|
61
|
-
const source = ${S};
|
|
62
|
-
const input = ${I};
|
|
63
|
-
const flags = ${F};
|
|
64
|
-
try {
|
|
65
|
-
const re = new RegExp(source, flags);
|
|
66
|
-
const match = re.exec(input);
|
|
67
|
-
// parentPort.postMessage, NOT bare postMessage: with eval:true
|
|
68
|
-
// workers this Node version does not expose the bare postMessage
|
|
69
|
-
// global \u2014 the worker throws ReferenceError at startup and the
|
|
70
|
-
// host misreads it as a timeout (positive-path regression).
|
|
71
|
-
parentPort.postMessage({ ok: true, match });
|
|
72
|
-
} catch (err) {
|
|
73
|
-
parentPort.postMessage({ ok: false, error: err && err.message ? err.message : String(err) });
|
|
74
|
-
}
|
|
75
|
-
`;
|
|
76
|
-
}
|
|
16
|
+
import {
|
|
17
|
+
withReDoSGuard,
|
|
18
|
+
guardedMatcher
|
|
19
|
+
} from "@wrongstack/plugin-sdk/runtime";
|
|
77
20
|
|
|
78
21
|
// src/path-guard/glob.ts
|
|
79
22
|
var GLOB_REDOS_BUDGET_MS = 250;
|
|
@@ -189,8 +132,8 @@ function isRootPathScope(path) {
|
|
|
189
132
|
function isDirectoryAmbiguousPath(path) {
|
|
190
133
|
const normalized = normalizePath(path).replace(/\/$/, "");
|
|
191
134
|
if (isRootPathScope(normalized)) return true;
|
|
192
|
-
const
|
|
193
|
-
return path.endsWith("/") ||
|
|
135
|
+
const basename = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
136
|
+
return path.endsWith("/") || basename.length > 0 && !basename.includes(".");
|
|
194
137
|
}
|
|
195
138
|
function hasConfiguredProtectedDescendant(path, patterns) {
|
|
196
139
|
const normalized = normalizePath(path).replace(/\/$/, "").toLowerCase();
|
|
@@ -1365,38 +1308,20 @@ function operationLabel(toolName) {
|
|
|
1365
1308
|
|
|
1366
1309
|
// src/path-guard/index.ts
|
|
1367
1310
|
import { realpathSync } from "node:fs";
|
|
1368
|
-
import { resolve
|
|
1369
|
-
|
|
1370
|
-
// src/runtime/index.ts
|
|
1371
|
-
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
1372
|
-
|
|
1373
|
-
// src/runtime/local-bin.ts
|
|
1374
|
-
import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
|
|
1311
|
+
import { resolve } from "node:path";
|
|
1375
1312
|
|
|
1376
1313
|
// src/runtime/index.ts
|
|
1377
|
-
var
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
}
|
|
1381
|
-
function withinProjectPath(projectRoot, candidate) {
|
|
1382
|
-
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
1383
|
-
if (hasLeadingDash(candidate)) return false;
|
|
1384
|
-
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
1385
|
-
const rel = relative(projectRoot, resolved);
|
|
1386
|
-
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
1387
|
-
}
|
|
1388
|
-
function withinProject(p) {
|
|
1389
|
-
const cwd = process.cwd();
|
|
1390
|
-
return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
|
|
1391
|
-
}
|
|
1314
|
+
var runtime_exports = {};
|
|
1315
|
+
__reExport(runtime_exports, runtime_star);
|
|
1316
|
+
import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
|
|
1392
1317
|
|
|
1393
1318
|
// src/path-guard/index.ts
|
|
1394
1319
|
function isSymlinkEscape(path, cwd) {
|
|
1395
|
-
if (!withinProject(path)) return false;
|
|
1320
|
+
if (!(0, runtime_exports.withinProject)(path)) return false;
|
|
1396
1321
|
try {
|
|
1397
|
-
const abs =
|
|
1322
|
+
const abs = resolve(cwd ?? process.cwd(), path);
|
|
1398
1323
|
const real = realpathSync(abs);
|
|
1399
|
-
return !withinProject(real);
|
|
1324
|
+
return !(0, runtime_exports.withinProject)(real);
|
|
1400
1325
|
} catch {
|
|
1401
1326
|
return false;
|
|
1402
1327
|
}
|
package/dist/pr-drafter.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __copyProps = (to, from, except, desc) => {
|
|
6
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
7
|
+
for (let key of __getOwnPropNames(from))
|
|
8
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
9
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
10
|
+
}
|
|
11
|
+
return to;
|
|
12
|
+
};
|
|
13
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
14
|
+
|
|
1
15
|
// src/pr-drafter/index.ts
|
|
2
16
|
import { execFile } from "node:child_process";
|
|
3
17
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
18
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
5
19
|
|
|
6
|
-
// src/runtime/local-bin.ts
|
|
7
|
-
import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
|
|
8
|
-
|
|
9
|
-
// src/runtime/handles.ts
|
|
10
|
-
function releaseHandle(off) {
|
|
11
|
-
if (off) {
|
|
12
|
-
try {
|
|
13
|
-
off();
|
|
14
|
-
} catch {
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
20
|
// src/runtime/index.ts
|
|
21
|
-
var
|
|
21
|
+
var runtime_exports = {};
|
|
22
|
+
__reExport(runtime_exports, runtime_star);
|
|
23
|
+
import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
|
|
22
24
|
|
|
23
25
|
// src/pr-drafter/index.ts
|
|
24
26
|
var API_VERSION = "^0.1.10";
|
|
@@ -216,7 +218,7 @@ var plugin = {
|
|
|
216
218
|
state.draftsWritten = 0;
|
|
217
219
|
state.draftErrors = 0;
|
|
218
220
|
state.stopInvocations = 0;
|
|
219
|
-
state.stopHookUnregister = releaseHandle(state.stopHookUnregister);
|
|
221
|
+
state.stopHookUnregister = (0, runtime_exports.releaseHandle)(state.stopHookUnregister);
|
|
220
222
|
for (const off of state.eventUnsubscribers) {
|
|
221
223
|
try {
|
|
222
224
|
off();
|
package/dist/prompt-firewall.js
CHANGED
|
@@ -2,213 +2,16 @@
|
|
|
2
2
|
import { performance } from "node:perf_hooks";
|
|
3
3
|
|
|
4
4
|
// src/runtime/credential-patterns.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g
|
|
10
|
-
},
|
|
11
|
-
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?!ant)(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
12
|
-
// GitHub. `ghp_` is only the personal-access-token prefix — the OAuth
|
|
13
|
-
// (`gho_`), user-to-server (`ghu_`), server-to-server (`ghs_`) and
|
|
14
|
-
// refresh (`ghr_`) tokens grant the same or broader access and were
|
|
15
|
-
// previously not detected at all.
|
|
16
|
-
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
|
|
17
|
-
{
|
|
18
|
-
type: "github_oauth_token",
|
|
19
|
-
regex: /(?<![A-Za-z0-9])gh[ousr]_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g
|
|
20
|
-
},
|
|
21
|
-
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
|
|
22
|
-
// GitLab
|
|
23
|
-
{ type: "gitlab_pat", regex: /(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
24
|
-
{
|
|
25
|
-
type: "gitlab_runner_token",
|
|
26
|
-
regex: /(?<![A-Za-z0-9])glrt-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
27
|
-
},
|
|
28
|
-
// npm — a leaked publish token is a supply-chain compromise.
|
|
29
|
-
{ type: "npm_token", regex: /(?<![A-Za-z0-9])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g },
|
|
30
|
-
// AWS
|
|
31
|
-
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
|
|
32
|
-
// GCP
|
|
33
|
-
{ type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
|
|
34
|
-
// Slack. `xoxe` (token-rotation) and `xapp` (app-level) were missing;
|
|
35
|
-
// both are as sensitive as the bot/user tokens already covered.
|
|
36
|
-
{
|
|
37
|
-
type: "slack_token",
|
|
38
|
-
regex: /(?<![A-Za-z0-9-])xox[abposer]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g
|
|
39
|
-
},
|
|
40
|
-
{ type: "slack_app_token", regex: /(?<![A-Za-z0-9-])xapp-\d-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
|
|
41
|
-
{
|
|
42
|
-
type: "slack_webhook",
|
|
43
|
-
regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Za-z0-9_-]+\/B[A-Za-z0-9_-]+\/[A-Za-z0-9]{16,}/g
|
|
44
|
-
},
|
|
45
|
-
// Stripe
|
|
46
|
-
{
|
|
47
|
-
type: "stripe_key",
|
|
48
|
-
regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g
|
|
49
|
-
},
|
|
50
|
-
// Twilio
|
|
51
|
-
{ type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
|
|
52
|
-
// Telegram
|
|
53
|
-
{
|
|
54
|
-
type: "telegram_bot_token",
|
|
55
|
-
regex: /(?:(?<![A-Za-z0-9_])|(?<=(?:^|[^A-Za-z0-9_])bot))\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
56
|
-
},
|
|
57
|
-
// JWT
|
|
58
|
-
{
|
|
59
|
-
type: "jwt",
|
|
60
|
-
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
|
|
61
|
-
},
|
|
62
|
-
// Private keys
|
|
63
|
-
{
|
|
64
|
-
type: "private_key",
|
|
65
|
-
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
|
|
66
|
-
},
|
|
67
|
-
// AI/ML provider tokens
|
|
68
|
-
{ type: "huggingface_token", regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g },
|
|
69
|
-
{ type: "replicate_token", regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
70
|
-
{ type: "perplexity_key", regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
71
|
-
{ type: "groq_key", regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
72
|
-
// SaaS / infrastructure tokens — each grants API access on the user's
|
|
73
|
-
// account, and each was previously invisible to this gate.
|
|
74
|
-
{ type: "sendgrid_key", regex: /(?<![A-Za-z0-9])SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
75
|
-
{ type: "digitalocean_token", regex: /(?<![A-Za-z0-9])dop_v1_[a-f0-9]{64}(?![A-Za-z0-9])/g },
|
|
76
|
-
{ type: "doppler_token", regex: /(?<![A-Za-z0-9])dp\.(?:pt|st|sa|scim|audit)\.[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
77
|
-
{ type: "shopify_token", regex: /(?<![A-Za-z0-9])shp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}(?![A-Za-z0-9])/g },
|
|
78
|
-
{ type: "docker_pat", regex: /(?<![A-Za-z0-9])dckr_pat_[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
79
|
-
{ type: "linear_key", regex: /(?<![A-Za-z0-9])lin_api_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
80
|
-
{ type: "atlassian_token", regex: /(?<![A-Za-z0-9])ATATT3[A-Za-z0-9_\-=]{40,}(?![A-Za-z0-9_\-=])/g },
|
|
81
|
-
{ type: "square_token", regex: /(?<![A-Za-z0-9])(?:sq0(?:atp|csp)-|EAAA)[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
82
|
-
{
|
|
83
|
-
type: "azure_storage_key",
|
|
84
|
-
regex: /AccountKey=[A-Za-z0-9+/]{80,}={0,2}/g
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
type: "google_oauth_client_secret",
|
|
88
|
-
regex: /(?<![A-Za-z0-9_-])GOCSPX-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
89
|
-
},
|
|
90
|
-
// Bearer tokens
|
|
91
|
-
{
|
|
92
|
-
type: "bearer_token",
|
|
93
|
-
regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?![A-Za-z0-9._~+/-])/g
|
|
94
|
-
},
|
|
95
|
-
// Database URIs. Require password-bearing user-info; credential-free values stay scannable.
|
|
96
|
-
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
|
|
97
|
-
{
|
|
98
|
-
type: "postgres_uri",
|
|
99
|
-
// Query parsers decode percent-encoded parameter names. Recognize each
|
|
100
|
-
// encoded character in `password` so mixed forms such as `pass%77ord`
|
|
101
|
-
// cannot bypass detection while keeping the scan strictly bounded.
|
|
102
|
-
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
|
|
103
|
-
},
|
|
104
|
-
{ type: "mysql_uri", regex: /mysql:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
|
|
105
|
-
{ type: "redis_uri", regex: /redis:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
|
|
106
|
-
{
|
|
107
|
-
// Credentials serialised as JSON, keyed rather than prefixed. Every other
|
|
108
|
-
// entry in this table recognises a credential by its SHAPE (`ghp_`, `sk-`,
|
|
109
|
-
// `eyJ`), which means a key with no distinctive prefix — Azure, a
|
|
110
|
-
// self-hosted gateway, an Anthropic/Codex OAuth token — was invisible to
|
|
111
|
-
// both surfaces. `prompt-firewall` guards the outgoing provider request, so
|
|
112
|
-
// this is what stops a JSON-shaped tool result carrying such a value to a
|
|
113
|
-
// third party.
|
|
114
|
-
//
|
|
115
|
-
// The key is matched in a LOOKBEHIND, so the reported match is the secret
|
|
116
|
-
// itself and the pattern keeps zero capturing groups — `secret-scanner`
|
|
117
|
-
// maps a combined-regex group index back to the pattern that fired, and an
|
|
118
|
-
// inner group would shift that mapping (see the style note above, enforced
|
|
119
|
-
// by credential-pattern-parity.test.ts).
|
|
120
|
-
//
|
|
121
|
-
// Mirrors `json_credential_key` in
|
|
122
|
-
// `@wrongstack/core` → `src/security/secret-scrubber.ts`. Keep the two key
|
|
123
|
-
// lists in step; the core side additionally preserves the key name when it
|
|
124
|
-
// rewrites, which is why it is written with capture groups instead.
|
|
125
|
-
type: "json_credential_key",
|
|
126
|
-
regex: /(?<="[A-Za-z0-9_]{0,64}(?:apiKey|api_key|token|secret|password|authorization|bearer|private_key|access_token|refresh_token|client_secret)"\s{0,8}:\s{0,8}")[^"\\]{8,512}(?=")/gi
|
|
127
|
-
}
|
|
128
|
-
];
|
|
129
|
-
function cloneCredentialPatterns() {
|
|
130
|
-
return CREDENTIAL_PATTERNS.map((p) => ({
|
|
131
|
-
type: p.type,
|
|
132
|
-
regex: new RegExp(p.regex.source, p.regex.flags)
|
|
133
|
-
}));
|
|
134
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
cloneCredentialPatterns,
|
|
7
|
+
CREDENTIAL_PATTERNS
|
|
8
|
+
} from "@wrongstack/plugin-sdk/runtime";
|
|
135
9
|
|
|
136
10
|
// src/runtime/redos-guard.ts
|
|
137
|
-
import {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const workerSource = buildWorkerSource(re.source, input, re.flags);
|
|
142
|
-
const worker = new Worker(workerSource, {
|
|
143
|
-
eval: true,
|
|
144
|
-
name: `redos-guard:${re.source.slice(0, 32)}`
|
|
145
|
-
});
|
|
146
|
-
return new Promise((resolve) => {
|
|
147
|
-
let settled = false;
|
|
148
|
-
const onMessage = (msg) => {
|
|
149
|
-
if (settled) return;
|
|
150
|
-
settled = true;
|
|
151
|
-
clearTimeout(timer);
|
|
152
|
-
worker.terminate().catch(() => {
|
|
153
|
-
});
|
|
154
|
-
if (!msg.ok) {
|
|
155
|
-
resolve({ timedOut: true, match: null });
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
resolve({ timedOut: false, match: msg.match });
|
|
159
|
-
};
|
|
160
|
-
const onError = () => {
|
|
161
|
-
if (settled) return;
|
|
162
|
-
settled = true;
|
|
163
|
-
clearTimeout(timer);
|
|
164
|
-
worker.terminate().catch(() => {
|
|
165
|
-
});
|
|
166
|
-
resolve({ timedOut: true, match: null });
|
|
167
|
-
};
|
|
168
|
-
const timer = setTimeout(() => {
|
|
169
|
-
if (settled) return;
|
|
170
|
-
settled = true;
|
|
171
|
-
const elapsedMs = Date.now() - start;
|
|
172
|
-
worker.terminate().catch(() => {
|
|
173
|
-
});
|
|
174
|
-
try {
|
|
175
|
-
opts.onTimeout?.({
|
|
176
|
-
regex: re,
|
|
177
|
-
input,
|
|
178
|
-
budgetMs: opts.budgetMs,
|
|
179
|
-
elapsedMs
|
|
180
|
-
});
|
|
181
|
-
} catch {
|
|
182
|
-
}
|
|
183
|
-
resolve({ timedOut: true, match: null });
|
|
184
|
-
}, opts.budgetMs);
|
|
185
|
-
timer.unref?.();
|
|
186
|
-
worker.on("message", onMessage);
|
|
187
|
-
worker.on("error", onError);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
function buildWorkerSource(source, input, flags) {
|
|
191
|
-
const S = JSON.stringify(source);
|
|
192
|
-
const I = JSON.stringify(input);
|
|
193
|
-
const F = JSON.stringify(flags);
|
|
194
|
-
return `
|
|
195
|
-
const { parentPort } = require('node:worker_threads');
|
|
196
|
-
const source = ${S};
|
|
197
|
-
const input = ${I};
|
|
198
|
-
const flags = ${F};
|
|
199
|
-
try {
|
|
200
|
-
const re = new RegExp(source, flags);
|
|
201
|
-
const match = re.exec(input);
|
|
202
|
-
// parentPort.postMessage, NOT bare postMessage: with eval:true
|
|
203
|
-
// workers this Node version does not expose the bare postMessage
|
|
204
|
-
// global \u2014 the worker throws ReferenceError at startup and the
|
|
205
|
-
// host misreads it as a timeout (positive-path regression).
|
|
206
|
-
parentPort.postMessage({ ok: true, match });
|
|
207
|
-
} catch (err) {
|
|
208
|
-
parentPort.postMessage({ ok: false, error: err && err.message ? err.message : String(err) });
|
|
209
|
-
}
|
|
210
|
-
`;
|
|
211
|
-
}
|
|
11
|
+
import {
|
|
12
|
+
withReDoSGuard,
|
|
13
|
+
guardedMatcher
|
|
14
|
+
} from "@wrongstack/plugin-sdk/runtime";
|
|
212
15
|
|
|
213
16
|
// src/prompt-firewall/index.ts
|
|
214
17
|
var KIND_ALIASES = {
|
|
@@ -1,164 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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();
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __copyProps = (to, from, except, desc) => {
|
|
6
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
7
|
+
for (let key of __getOwnPropNames(from))
|
|
8
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
9
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
102
10
|
}
|
|
11
|
+
return to;
|
|
103
12
|
};
|
|
13
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
14
|
+
|
|
15
|
+
// src/refactor-suggester/index.ts
|
|
16
|
+
import { readFile } from "node:fs/promises";
|
|
17
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
104
18
|
|
|
105
19
|
// src/runtime/index.ts
|
|
106
|
-
var
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
function withinProjectPath(projectRoot, candidate) {
|
|
111
|
-
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
112
|
-
if (hasLeadingDash(candidate)) return false;
|
|
113
|
-
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
114
|
-
const rel = relative(projectRoot, resolved);
|
|
115
|
-
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
116
|
-
}
|
|
117
|
-
function withinProject(p) {
|
|
118
|
-
const cwd = process.cwd();
|
|
119
|
-
return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
|
|
120
|
-
}
|
|
121
|
-
var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
|
|
122
|
-
async function collectSourceFilesAsync(root, opts) {
|
|
123
|
-
const { readdir, stat } = await import("node:fs/promises");
|
|
124
|
-
const files = [];
|
|
125
|
-
try {
|
|
126
|
-
const s = await stat(root);
|
|
127
|
-
if (s.isFile()) {
|
|
128
|
-
if (matchesExtension(root, opts.extensions)) files.push(root);
|
|
129
|
-
return files;
|
|
130
|
-
}
|
|
131
|
-
if (!s.isDirectory()) return files;
|
|
132
|
-
} catch {
|
|
133
|
-
return files;
|
|
134
|
-
}
|
|
135
|
-
const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
136
|
-
const excludeSet = new Set(exclude);
|
|
137
|
-
async function walk(dir, depth) {
|
|
138
|
-
if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
|
|
139
|
-
let entries;
|
|
140
|
-
try {
|
|
141
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
142
|
-
} catch {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
146
|
-
for (const entry of entries) {
|
|
147
|
-
if (excludeSet.has(entry.name)) continue;
|
|
148
|
-
const full = resolve(dir, entry.name);
|
|
149
|
-
if (entry.isDirectory()) {
|
|
150
|
-
await walk(full, depth + 1);
|
|
151
|
-
} else if (entry.isFile() && matchesExtension(full, opts.extensions)) {
|
|
152
|
-
files.push(full);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
await walk(root, 0);
|
|
157
|
-
return files;
|
|
158
|
-
}
|
|
159
|
-
function matchesExtension(p, exts) {
|
|
160
|
-
return exts.includes(extname(p).toLowerCase());
|
|
161
|
-
}
|
|
20
|
+
var runtime_exports = {};
|
|
21
|
+
__reExport(runtime_exports, runtime_star);
|
|
22
|
+
import * as runtime_star from "@wrongstack/plugin-sdk/runtime";
|
|
162
23
|
|
|
163
24
|
// src/refactor-suggester/index.ts
|
|
164
25
|
var API_VERSION = "^0.1.10";
|
|
@@ -170,7 +31,7 @@ var state = {
|
|
|
170
31
|
warningCount: 0,
|
|
171
32
|
errorCount: 0,
|
|
172
33
|
hookUnregister: null,
|
|
173
|
-
lastHookWarning: new BoundedMap({ max: 512, ttlMs: HOOK_WARNING_COOLDOWN_MS })
|
|
34
|
+
lastHookWarning: new runtime_exports.BoundedMap({ max: 512, ttlMs: HOOK_WARNING_COOLDOWN_MS })
|
|
174
35
|
};
|
|
175
36
|
var DEFAULTS = {
|
|
176
37
|
enabled: false,
|
|
@@ -204,7 +65,7 @@ function toPosix(p) {
|
|
|
204
65
|
return p.replace(/\\/g, "/");
|
|
205
66
|
}
|
|
206
67
|
function relativePath(p) {
|
|
207
|
-
return toPosix(
|
|
68
|
+
return toPosix(relative(process.cwd(), p));
|
|
208
69
|
}
|
|
209
70
|
function leadingIndentLevel(line) {
|
|
210
71
|
const leading = line.match(/^(\s*)/)?.[1] ?? "";
|
|
@@ -291,9 +152,9 @@ function detectSmells(filePath, content, rules) {
|
|
|
291
152
|
}
|
|
292
153
|
async function scanPath(rawPath, cfg) {
|
|
293
154
|
const root = process.cwd();
|
|
294
|
-
const resolved =
|
|
155
|
+
const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
|
|
295
156
|
const exts = normalizeExtensions(cfg.extensions);
|
|
296
|
-
const files = await collectSourceFilesAsync(resolved, { extensions: exts });
|
|
157
|
+
const files = await (0, runtime_exports.collectSourceFilesAsync)(resolved, { extensions: exts });
|
|
297
158
|
const suggestions = [];
|
|
298
159
|
let scannedFiles = 0;
|
|
299
160
|
let truncated = false;
|
|
@@ -371,14 +232,14 @@ var plugin = {
|
|
|
371
232
|
const inp = input.toolInput ?? {};
|
|
372
233
|
const sourcePath = inp["path"];
|
|
373
234
|
if (!sourcePath || typeof sourcePath !== "string") return;
|
|
374
|
-
if (!withinProject(sourcePath)) return;
|
|
235
|
+
if (!(0, runtime_exports.withinProject)(sourcePath)) return;
|
|
375
236
|
const exts = normalizeExtensions(cfg.extensions);
|
|
376
|
-
if (!matchesExtension(sourcePath, exts)) return;
|
|
237
|
+
if (!(0, runtime_exports.matchesExtension)(sourcePath, exts)) return;
|
|
377
238
|
state.hookInvocationCount += 1;
|
|
378
239
|
const now = Date.now();
|
|
379
240
|
const lastWarning = state.lastHookWarning.get(sourcePath);
|
|
380
241
|
if (lastWarning !== void 0 && now - lastWarning < HOOK_WARNING_COOLDOWN_MS) return;
|
|
381
|
-
const resolved =
|
|
242
|
+
const resolved = resolve(process.cwd(), sourcePath);
|
|
382
243
|
let content;
|
|
383
244
|
try {
|
|
384
245
|
content = await readFile(resolved, "utf-8");
|
|
@@ -411,7 +272,7 @@ var plugin = {
|
|
|
411
272
|
async execute(input) {
|
|
412
273
|
if (!cfg.enabled) return { ok: false, error: "refactor-suggester is disabled" };
|
|
413
274
|
const rawPath = typeof input.path === "string" ? input.path : ".";
|
|
414
|
-
if (!withinProject(rawPath)) {
|
|
275
|
+
if (!(0, runtime_exports.withinProject)(rawPath)) {
|
|
415
276
|
return { ok: false, error: "path is outside the project root" };
|
|
416
277
|
}
|
|
417
278
|
state.scanCount += 1;
|
|
@@ -425,7 +286,7 @@ var plugin = {
|
|
|
425
286
|
state.suggestionCount += result.suggestions.length;
|
|
426
287
|
return {
|
|
427
288
|
ok: true,
|
|
428
|
-
path: relativePath(
|
|
289
|
+
path: relativePath(resolve(process.cwd(), rawPath)),
|
|
429
290
|
scannedFiles: result.scannedFiles,
|
|
430
291
|
discoveredFiles: result.discoveredFiles,
|
|
431
292
|
// Say so when the cap stopped the walk early: a partial scan
|