guardvibe 3.14.2 → 3.16.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/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/build/tools/ast-engine.d.ts +18 -0
- package/build/tools/ast-engine.js +214 -0
- package/build/tools/check-code.js +18 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to GuardVibe are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.16.0] - 2026-06-08
|
|
9
|
+
|
|
10
|
+
### Added — FAZ 3 part 2: AST BOLA ownership-guard detection for VG950 (442 rules / 37 tools)
|
|
11
|
+
- **VG950 (BOLA — find-by-id without ownership) is now AST-aware.** It had no ownership handling at all (VG951 already excludes where-clause ownership). `bolaOwnershipGuarded` suppresses VG950 when the AST proves the query is ownership-guarded — EITHER an ownership field in the **WHERE clause** with a non-route-param value, OR a same-function **post-fetch ownership comparison** of the fetched resource against the session (e.g. `if (eventType.userId !== ctx.user.id) throw`).
|
|
12
|
+
- **Precise where the regex can't be:** it ignores a `userId` that only appears in `select` (a regex lookahead would suppress on that → false negative), it sees an ownership check that lives in a separate statement, and it refuses to count an ownership field whose value is itself a route param (still BOLA).
|
|
13
|
+
- **Validated (clean stash diff): VG950 22 → 15; all 7 removed are genuinely ownership-guarded** — 3 via where-clause ownership (dub rewards/oauth-apps/tokens), 4 via post-fetch comparison (cal schedule/ooo handlers, each with a real `…userId !== user.id` check). **0 true BOLA hidden (0 false negatives), 0 false positives introduced.** 10 new tests.
|
|
14
|
+
- No rule or tool changes (442 / 37). FAZ 3 part 2 (engine reused from part 1).
|
|
15
|
+
|
|
16
|
+
Gate green (build / lint / test / self-audit PASS / A / 0).
|
|
17
|
+
|
|
18
|
+
## [3.15.0] - 2026-06-08
|
|
19
|
+
|
|
20
|
+
### Added — FAZ 3 part 1: AST dataflow engine + precise VG406 (442 rules / 37 tools)
|
|
21
|
+
- **New AST/dataflow engine** (`src/tools/ast-engine.ts`), backed by the TypeScript compiler (loaded lazily, used only on the AST path). Brings real intra-file dataflow the line/regex engine structurally can't do.
|
|
22
|
+
- **VG406 (Unsanitized Dynamic Route Params) is now dataflow-aware.** Its regex bridged a `params`/`searchParams` access to ANY later DB sink via an unbounded match, false-positiving when the param never flows to that sink. `paramReachesSink` does intra-procedural taint — seeding from params/searchParams and propagating through variable assignments and query-builder calls — so VG406 fires only on a real param → sink flow (multi-hop included, the case a name-only regex misses).
|
|
23
|
+
- **Validated (clean stash diff): VG406 24 → 20; all 4 removed are confirmed false positives** where `params` is a function/constructor/callback argument named "params" (not a route param) — dub `get-events`/`create-bounty-submission`, plane `filter.store`, unkey `use-logs-query`. **0 true positives lost, 0 new findings.** 10 tests (engine + integration).
|
|
24
|
+
- **Runtime dependency:** added `typescript` (^5.7.0) — pure-JS, zero sub-dependencies, no native bindings, deterministic everywhere. The README claim is updated from "zero runtime dependencies" to "minimal, fully-audited runtime dependencies (MCP SDK, Zod, TypeScript compiler)". The publish workflow's npm step is already idempotent.
|
|
25
|
+
- No rule or tool changes (442 / 37). First of several FAZ 3 releases (next: extend the engine to IDOR/BOLA and VG950).
|
|
26
|
+
|
|
27
|
+
Gate green (build / lint / test / self-audit PASS / A / 0).
|
|
28
|
+
|
|
8
29
|
## [3.14.2] - 2026-06-08
|
|
9
30
|
|
|
10
31
|
### Fixed — VG964 false positives on App Router route segments (442 rules / 37 tools)
|
package/README.md
CHANGED
|
@@ -584,7 +584,7 @@ GuardVibe takes supply chain security seriously:
|
|
|
584
584
|
- **Branch protection** — force push disabled on main, admin enforcement enabled
|
|
585
585
|
- **Tag protection** — version tags (`v*`) cannot be deleted or force-pushed
|
|
586
586
|
- **Minimal CI permissions** — GitHub Actions workflows use `permissions: contents: read` only
|
|
587
|
-
- **
|
|
587
|
+
- **Minimal, fully-audited runtime dependencies** — only the MCP SDK, Zod, and the TypeScript compiler (used for AST-based dataflow analysis). All three are widely-audited, zero-sub-dependency packages — no native bindings, no obscure transitive deps
|
|
588
588
|
|
|
589
589
|
To report a vulnerability, please email info@goklab.com or open a GitHub issue.
|
|
590
590
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True if a route parameter (params / searchParams) reaches a DB/query sink in
|
|
3
|
+
* this file, following assignments and query-builder calls. Returns true (the safe
|
|
4
|
+
* default — don't suppress) when TypeScript is unavailable or parsing fails, so
|
|
5
|
+
* the rule keeps its prior behavior rather than silently hiding a finding.
|
|
6
|
+
*/
|
|
7
|
+
export declare function paramReachesSink(code: string, filePath?: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* BOLA ownership-guard detection for VG950 (find-by-user-id). Returns true (the
|
|
10
|
+
* query is ownership-guarded → suppress the finding) when EITHER:
|
|
11
|
+
* (1) the find call's WHERE clause (not select!) contains an ownership field
|
|
12
|
+
* whose value is not itself a route param, OR
|
|
13
|
+
* (2) the enclosing function performs a post-fetch ownership comparison of an
|
|
14
|
+
* ownership field against a session/user value.
|
|
15
|
+
* Returns false on uncertainty (no parser, no matching call) so the rule keeps
|
|
16
|
+
* firing — for a BOLA rule we prefer a false positive over hiding a real one.
|
|
17
|
+
*/
|
|
18
|
+
export declare function bolaOwnershipGuarded(code: string, filePath: string | undefined, line: number): boolean;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// guardvibe-ignore — AST-engine helpers; the sink-method names and taint regex
|
|
2
|
+
// below are detector patterns, not vulnerable code.
|
|
3
|
+
/**
|
|
4
|
+
* AST engine (FAZ 3) — real parsing + intra-file dataflow for precision the
|
|
5
|
+
* line/regex engine structurally can't reach.
|
|
6
|
+
*
|
|
7
|
+
* Backed by the TypeScript compiler's parser, loaded LAZILY and synchronously
|
|
8
|
+
* (createRequire) so non-AST scan paths pay nothing for it. `typescript` is a
|
|
9
|
+
* pure-JS, zero-sub-dependency, no-native-bindings package — the lowest
|
|
10
|
+
* supply-chain-risk parser available — and is a bundled runtime dependency so the
|
|
11
|
+
* analysis is deterministic everywhere (not dependent on the scanned project
|
|
12
|
+
* happening to have its own copy). If it can't be loaded, every helper degrades
|
|
13
|
+
* to a safe default that preserves the prior (regex-only) behavior.
|
|
14
|
+
*
|
|
15
|
+
* First capability: precise param → sink reachability for VG406. The rule's regex
|
|
16
|
+
* bridges a `params`/`searchParams` access to ANY later DB sink in the file via an
|
|
17
|
+
* unbounded `[\s\S]*?`, so it false-positives when the param never actually flows
|
|
18
|
+
* to that sink. `paramReachesSink` does real intra-procedural taint — seeding from
|
|
19
|
+
* params/searchParams and propagating through variable assignments and
|
|
20
|
+
* query-builder calls — so a param routed through an intermediate variable still
|
|
21
|
+
* counts (the case a name-only regex misses) while an unrelated sink does not.
|
|
22
|
+
*/
|
|
23
|
+
import { createRequire } from "module";
|
|
24
|
+
let _ts = null;
|
|
25
|
+
let _loadFailed = false;
|
|
26
|
+
function getTs() {
|
|
27
|
+
if (_ts)
|
|
28
|
+
return _ts;
|
|
29
|
+
if (_loadFailed)
|
|
30
|
+
return null;
|
|
31
|
+
try {
|
|
32
|
+
const require = createRequire(import.meta.url);
|
|
33
|
+
_ts = require("typescript");
|
|
34
|
+
return _ts;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
_loadFailed = true;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function scriptKindFor(ts, filePath) {
|
|
42
|
+
const f = (filePath ?? "").toLowerCase();
|
|
43
|
+
if (f.endsWith(".tsx"))
|
|
44
|
+
return ts.ScriptKind.TSX;
|
|
45
|
+
if (f.endsWith(".jsx"))
|
|
46
|
+
return ts.ScriptKind.JSX;
|
|
47
|
+
if (f.endsWith(".js") || f.endsWith(".mjs") || f.endsWith(".cjs"))
|
|
48
|
+
return ts.ScriptKind.JS;
|
|
49
|
+
return ts.ScriptKind.TS;
|
|
50
|
+
}
|
|
51
|
+
// DB / query sink method names (last identifier of the callee).
|
|
52
|
+
const SINK_METHODS = new Set([
|
|
53
|
+
"query", "execute", "findUnique", "findFirst", "findMany", "delete", "update", "create",
|
|
54
|
+
"upsert", "aggregate", "count", "groupBy", "createMany", "updateMany", "deleteMany",
|
|
55
|
+
"raw", "$queryRaw", "$executeRaw", "$queryRawUnsafe", "$executeRawUnsafe",
|
|
56
|
+
]);
|
|
57
|
+
const PARAM_ROOT = /\b(?:params|searchParams)\b/;
|
|
58
|
+
/** Does `text` reference a taint root (params/searchParams) or any tainted identifier? */
|
|
59
|
+
function refsTaint(text, tainted) {
|
|
60
|
+
if (PARAM_ROOT.test(text))
|
|
61
|
+
return true;
|
|
62
|
+
for (const t of tainted) {
|
|
63
|
+
if (new RegExp("\\b" + t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\b").test(text))
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* True if a route parameter (params / searchParams) reaches a DB/query sink in
|
|
70
|
+
* this file, following assignments and query-builder calls. Returns true (the safe
|
|
71
|
+
* default — don't suppress) when TypeScript is unavailable or parsing fails, so
|
|
72
|
+
* the rule keeps its prior behavior rather than silently hiding a finding.
|
|
73
|
+
*/
|
|
74
|
+
export function paramReachesSink(code, filePath) {
|
|
75
|
+
const ts = getTs();
|
|
76
|
+
if (!ts)
|
|
77
|
+
return true;
|
|
78
|
+
let sf;
|
|
79
|
+
try {
|
|
80
|
+
sf = ts.createSourceFile(filePath ?? "file.ts", code, ts.ScriptTarget.Latest, true, scriptKindFor(ts, filePath));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
const namesFromBinding = (name) => {
|
|
86
|
+
if (ts.isIdentifier(name))
|
|
87
|
+
return [name.text];
|
|
88
|
+
const out = [];
|
|
89
|
+
if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
90
|
+
for (const el of name.elements) {
|
|
91
|
+
if (ts.isBindingElement(el))
|
|
92
|
+
out.push(...namesFromBinding(el.name));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
};
|
|
97
|
+
const assigns = [];
|
|
98
|
+
const sinkArgs = [];
|
|
99
|
+
const visit = (node) => {
|
|
100
|
+
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
101
|
+
assigns.push({ names: namesFromBinding(node.name), rhs: node.initializer.getText(sf) });
|
|
102
|
+
}
|
|
103
|
+
else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isIdentifier(node.left)) {
|
|
104
|
+
assigns.push({ names: [node.left.text], rhs: node.right.getText(sf) });
|
|
105
|
+
}
|
|
106
|
+
else if (ts.isCallExpression(node)) {
|
|
107
|
+
const callee = node.expression;
|
|
108
|
+
let method;
|
|
109
|
+
if (ts.isPropertyAccessExpression(callee))
|
|
110
|
+
method = callee.name.text;
|
|
111
|
+
else if (ts.isIdentifier(callee))
|
|
112
|
+
method = callee.text;
|
|
113
|
+
if (method && SINK_METHODS.has(method) && node.arguments.length > 0) {
|
|
114
|
+
sinkArgs.push(node.arguments.map(a => a.getText(sf)).join(", "));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
ts.forEachChild(node, visit);
|
|
118
|
+
};
|
|
119
|
+
visit(sf);
|
|
120
|
+
// Fixpoint taint propagation through assignments.
|
|
121
|
+
const tainted = new Set();
|
|
122
|
+
let changed = true;
|
|
123
|
+
let guard = 0;
|
|
124
|
+
while (changed && guard++ < 25) {
|
|
125
|
+
changed = false;
|
|
126
|
+
for (const a of assigns) {
|
|
127
|
+
if (refsTaint(a.rhs, tainted)) {
|
|
128
|
+
for (const n of a.names) {
|
|
129
|
+
if (!tainted.has(n)) {
|
|
130
|
+
tainted.add(n);
|
|
131
|
+
changed = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return sinkArgs.some(args => refsTaint(args, tainted));
|
|
138
|
+
}
|
|
139
|
+
const FIND_METHODS = new Set(["findUnique", "findFirst", "findById", "findOne", "getOne"]);
|
|
140
|
+
const OWNERSHIP_FIELDS = new Set([
|
|
141
|
+
"userId", "user_id", "ownerId", "owner_id", "authorId", "author_id", "createdById", "createdBy", "created_by",
|
|
142
|
+
"accountId", "account_id", "tenantId", "tenant_id", "orgId", "org_id", "organizationId",
|
|
143
|
+
"projectId", "project_id", "workspaceId", "workspace_id", "teamId", "team_id", "memberId", "member_id",
|
|
144
|
+
"programId", "customerId",
|
|
145
|
+
]);
|
|
146
|
+
// A comparison of a fetched resource's ownership field, on a line that also references a session/user.
|
|
147
|
+
const OWNERSHIP_COMPARE = /\.\s*(?:userId|ownerId|authorId|createdById|teamId|workspaceId|orgId|organizationId|tenantId|memberId|accountId|projectId)\b\s*(?:===|!==|==|!=)/i;
|
|
148
|
+
const SESSION_REF = /\b(?:session|ctx|auth|currentUser|viewer|member|account|workspace|team|org|self|me|user)\b/i;
|
|
149
|
+
/**
|
|
150
|
+
* BOLA ownership-guard detection for VG950 (find-by-user-id). Returns true (the
|
|
151
|
+
* query is ownership-guarded → suppress the finding) when EITHER:
|
|
152
|
+
* (1) the find call's WHERE clause (not select!) contains an ownership field
|
|
153
|
+
* whose value is not itself a route param, OR
|
|
154
|
+
* (2) the enclosing function performs a post-fetch ownership comparison of an
|
|
155
|
+
* ownership field against a session/user value.
|
|
156
|
+
* Returns false on uncertainty (no parser, no matching call) so the rule keeps
|
|
157
|
+
* firing — for a BOLA rule we prefer a false positive over hiding a real one.
|
|
158
|
+
*/
|
|
159
|
+
export function bolaOwnershipGuarded(code, filePath, line) {
|
|
160
|
+
const ts = getTs();
|
|
161
|
+
if (!ts)
|
|
162
|
+
return false;
|
|
163
|
+
let sf;
|
|
164
|
+
try {
|
|
165
|
+
sf = ts.createSourceFile(filePath ?? "file.ts", code, ts.ScriptTarget.Latest, true, scriptKindFor(ts, filePath));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
let target;
|
|
171
|
+
const visit = (node) => {
|
|
172
|
+
if (!target && ts.isCallExpression(node)) {
|
|
173
|
+
const callee = node.expression;
|
|
174
|
+
const method = ts.isPropertyAccessExpression(callee) ? callee.name.text
|
|
175
|
+
: ts.isIdentifier(callee) ? callee.text : undefined;
|
|
176
|
+
if (method && FIND_METHODS.has(method)) {
|
|
177
|
+
const startLine = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
178
|
+
if (Math.abs(startLine - line) <= 1)
|
|
179
|
+
target = node;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!target)
|
|
183
|
+
ts.forEachChild(node, visit);
|
|
184
|
+
};
|
|
185
|
+
visit(sf);
|
|
186
|
+
if (!target)
|
|
187
|
+
return false;
|
|
188
|
+
// (1) ownership field in the WHERE clause with a non-param value.
|
|
189
|
+
const arg0 = target.arguments[0];
|
|
190
|
+
if (arg0 && ts.isObjectLiteralExpression(arg0)) {
|
|
191
|
+
const whereProp = arg0.properties.find(p => ts.isPropertyAssignment(p) && p.name && ts.isIdentifier(p.name) && p.name.text === "where");
|
|
192
|
+
if (whereProp && ts.isPropertyAssignment(whereProp) && ts.isObjectLiteralExpression(whereProp.initializer)) {
|
|
193
|
+
for (const prop of whereProp.initializer.properties) {
|
|
194
|
+
const nm = prop.name && ts.isIdentifier(prop.name) ? prop.name.text : undefined;
|
|
195
|
+
if (nm && OWNERSHIP_FIELDS.has(nm)) {
|
|
196
|
+
const valText = ts.isPropertyAssignment(prop) ? prop.initializer.getText(sf) : nm;
|
|
197
|
+
if (!/\b(?:params|searchParams)\b/.test(valText))
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// (2) post-fetch ownership comparison against a session/user value, in the same function.
|
|
204
|
+
let fn = target;
|
|
205
|
+
while (fn && !(ts.isFunctionDeclaration(fn) || ts.isFunctionExpression(fn) || ts.isArrowFunction(fn) || ts.isMethodDeclaration(fn))) {
|
|
206
|
+
fn = fn.parent;
|
|
207
|
+
}
|
|
208
|
+
const body = fn ? fn.getText(sf) : code;
|
|
209
|
+
for (const ln of body.split("\n")) {
|
|
210
|
+
if (OWNERSHIP_COMPARE.test(ln) && SESSION_REF.test(ln))
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
@@ -4,6 +4,7 @@ import { loadConfig } from "../utils/config.js";
|
|
|
4
4
|
import { loadIgnoreFile, isIgnored } from "../utils/ignore.js";
|
|
5
5
|
import { securityBanner } from "../utils/banner.js";
|
|
6
6
|
import { looksMinified } from "../utils/constants.js";
|
|
7
|
+
import { paramReachesSink, bolaOwnershipGuarded } from "./ast-engine.js";
|
|
7
8
|
/** CVE version-pin rule IDs are VG900-VG931 (and only these). Other VG9xx IDs
|
|
8
9
|
* (VG983 Turso, VG990 SVG, VG998 OpenAI browser flag, etc.) are regular code-pattern
|
|
9
10
|
* rules and should NOT be exempted from comment / string-literal skip logic. */
|
|
@@ -674,6 +675,17 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
674
675
|
continue;
|
|
675
676
|
}
|
|
676
677
|
}
|
|
678
|
+
// VG406 (Unsanitized Dynamic Route Params): the regex bridges a params/searchParams
|
|
679
|
+
// access to ANY later DB sink in the file via an unbounded match, so it
|
|
680
|
+
// false-positives when the param never actually flows to that sink. Use AST
|
|
681
|
+
// dataflow (FAZ 3) to require a real param→sink flow — through assignments and
|
|
682
|
+
// query-builders, the case a name-only regex misses. Cheap guards gate the parse
|
|
683
|
+
// to files that actually contain a param + a sink (i.e. real VG406 candidates).
|
|
684
|
+
if (rule.id === "VG406" && filePath
|
|
685
|
+
&& /\b(?:params|searchParams)\b/.test(code)
|
|
686
|
+
&& /\b(?:query|execute|findUnique|findFirst|findMany|delete|update|create|upsert|aggregate|count|groupBy)\s*\(/.test(code)
|
|
687
|
+
&& !paramReachesSink(code, filePath))
|
|
688
|
+
continue;
|
|
677
689
|
// Skip SQL injection rules in schema/migration .sql files (DDL, not user input)
|
|
678
690
|
if (rule.id === "VG543" && (isMigrationFile || isSqlSchemaFile))
|
|
679
691
|
continue;
|
|
@@ -868,6 +880,12 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
868
880
|
const lineNumber = beforeMatch.split("\n").length;
|
|
869
881
|
if (isLineSuppressed(suppressions, lineNumber, rule.id))
|
|
870
882
|
continue;
|
|
883
|
+
// VG950 (BOLA find-by-id): suppress when AST proves the query is ownership-guarded —
|
|
884
|
+
// an ownership field in the WHERE clause (non-param value) or a post-fetch ownership
|
|
885
|
+
// comparison against the session. Precise where the regex can't be: it ignores a
|
|
886
|
+
// `userId` that only appears in `select`, and sees a separate comparison statement.
|
|
887
|
+
if (rule.id === "VG950" && filePath && bolaOwnershipGuarded(code, filePath, lineNumber))
|
|
888
|
+
continue;
|
|
871
889
|
// VG202: skip when the FROM target matches a previous AS-alias in the same file.
|
|
872
890
|
if (dockerStageAliases) {
|
|
873
891
|
const target = match[0].replace(/^FROM\s+/i, "").split(/[:@\s]/)[0].toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.0",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
5
|
"description": "Security infrastructure your AI can't be — deterministic, current past your model's training cutoff, whole-repo-aware, author-independent. Security MCP for vibe coding. 442 rules, 37 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. 71 CVE rules refreshed daily from GHSA/OSV/CISA KEV — Vite dev-server RCE, React Router 7 cluster, DOMPurify XSS, Better Auth bypass, Miasma @redhat-cloud-services compromise, Next.js May 2026 13-advisory cluster, Drizzle/MikroORM/Kysely SQL injection, Axios proxy-auth redirect leak, Hono setCookie attribute injection, Clerk SSRF, tRPC prototype pollution, @tanstack supply-chain, node-ipc protestware, OpenClaude sandbox bypass, plus the full AI-generated stack (Supabase, Stripe, Prisma, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK). 68 AI-native rules including OWASP MCP Top 10 tool-description prompt injection (VG1068), model-controlled sandbox-disable flag detection (VG1063), Session messenger exfil endpoint IOC (VG1075), and CI/CD supply-chain hardening (VG1070 npm --expect-provenance / --ignore-scripts enforcement).",
|
|
6
6
|
"type": "module",
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
},
|
|
108
108
|
"dependencies": {
|
|
109
109
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
110
|
+
"typescript": "^5.7.0",
|
|
110
111
|
"zod": "^3.25.0"
|
|
111
112
|
},
|
|
112
113
|
"overrides": {
|
|
@@ -119,7 +120,6 @@
|
|
|
119
120
|
"c8": "^11.0.0",
|
|
120
121
|
"eslint": "^10.2.0",
|
|
121
122
|
"tsx": "^4.21.0",
|
|
122
|
-
"typescript": "^5.7.0",
|
|
123
123
|
"typescript-eslint": "^8.58.0"
|
|
124
124
|
},
|
|
125
125
|
"engines": {
|