guardvibe 3.1.8 → 3.1.10
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.
|
@@ -9,9 +9,13 @@ const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
|
|
|
9
9
|
* route groups, and file name.
|
|
10
10
|
*/
|
|
11
11
|
function filePathToUrlPath(filePath) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// Strip everything up to and including the Next.js app directory.
|
|
13
|
+
// Covers: app/..., src/app/..., apps/<workspace>/app/..., apps/<workspace>/src/app/...,
|
|
14
|
+
// packages/<name>/app/... — common monorepo (Turborepo/pnpm) layouts where the
|
|
15
|
+
// route file lives under a workspace prefix that is not part of the URL.
|
|
16
|
+
let p = filePath.replace(/^.*?\/(?:src\/)?app\//, "");
|
|
17
|
+
// Fallback for simple non-monorepo paths.
|
|
18
|
+
p = p.replace(/^src\/app\//, "").replace(/^app\//, "");
|
|
15
19
|
// Remove file name (route.ts, page.tsx, layout.tsx)
|
|
16
20
|
p = p.replace(/\/(route|page|layout)\.(ts|tsx|js|jsx)$/, "");
|
|
17
21
|
// Remove route groups: (groupName)
|
|
@@ -80,7 +84,12 @@ export function enumerateRoutes(files) {
|
|
|
80
84
|
*/
|
|
81
85
|
export function parseMiddlewareMatchers(content) {
|
|
82
86
|
// Normalize literal escape sequences that AI assistants may pass
|
|
83
|
-
|
|
87
|
+
let normalized = content.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
88
|
+
// Strip block + line comments before pulling the matcher array. Real-world
|
|
89
|
+
// middleware files carry JSDoc-style notes inline (dub's matcher block has
|
|
90
|
+
// four bullet points); split-on-comma was swallowing those bullets into the
|
|
91
|
+
// matcher list, breaking every downstream regex test.
|
|
92
|
+
normalized = normalized.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
84
93
|
const stringMatch = /matcher\s*:\s*"([^"]+)"/.exec(normalized);
|
|
85
94
|
if (stringMatch)
|
|
86
95
|
return [stringMatch[1]];
|
|
@@ -775,6 +775,11 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
775
775
|
// (`forgot_password: "forgot_password_clicked"`).
|
|
776
776
|
if (filePath && /(?:\/i18n\/|\/locales?\/|\/translations?\/|\/event[-_]tracker\/|\/analytics\/events\/|\/messages\/[a-z]{2}(?:[-_][A-Z]{2})?\.[jt]sx?$)/i.test(filePath))
|
|
777
777
|
continue;
|
|
778
|
+
// Seed scripts and shared test-fixture builders deliberately use placeholder
|
|
779
|
+
// credentials (`password: "delete-me"`, `password: "MOCK_PASSWORD"`). These
|
|
780
|
+
// populate dev/CI databases and never run against production.
|
|
781
|
+
if (filePath && /(?:^|\/)(?:scripts|seeds?|fixtures?|__fixtures__|bookingScenario|setupAndTeardown)\b|(?:^|\/)seed[-_.][\w-]*\.[jt]sx?$/i.test(filePath))
|
|
782
|
+
continue;
|
|
778
783
|
}
|
|
779
784
|
// Skip credential rules when the variable name signals test/example/mock intent.
|
|
780
785
|
// e.g. `testingPassword`, `examplePassword`, `mockApiKey`, `placeholderSecret`.
|
|
@@ -787,6 +792,16 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
787
792
|
// Covers both SCREAMING_SNAKE (TS string enums) and snake_case (event-tracker key maps).
|
|
788
793
|
if (/\b([A-Za-z_][A-Za-z0-9_]*)\s*[:=]\s*["']\1["']/.test(matchedLine))
|
|
789
794
|
continue;
|
|
795
|
+
// Skip when the value is just a re-casing of the identifier — covers
|
|
796
|
+
// `IncorrectEmailPassword = "incorrect-email-password"` (TS string enum kebab) and
|
|
797
|
+
// `X_CAL_SECRET_KEY = "x-cal-secret-key"` (HTTP header constant). Both forms reduce
|
|
798
|
+
// to the same lowercase letters; no real credential is its own name re-cased.
|
|
799
|
+
const idValuePair = matchedLine.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*[:=]\s*["']([\w-]+)["']/);
|
|
800
|
+
if (idValuePair) {
|
|
801
|
+
const canonical = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
802
|
+
if (canonical(idValuePair[1]) === canonical(idValuePair[2]))
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
790
805
|
// Skip SCREAMING_SNAKE error/status codes whose value is digits-only.
|
|
791
806
|
// e.g. `INVALID_PASSWORD = "5020"` — error code, not a credential.
|
|
792
807
|
if (/\b[A-Z][A-Z0-9_]*\s*=\s*["']\d+["']/.test(matchedLine))
|
|
@@ -922,10 +937,20 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
922
937
|
const varName = match[0].split(/\s*(?:===|!==|==|!=)/)[0].trim();
|
|
923
938
|
if (/(?:Count|Length|Balance|Map|List|Array|Index|Size|Total|Num|Id|Type|Name|Status|Data|Info|Error|Result|Response|Config|Option|Url|Path|Provider|Model|Limit|Quota|Rate|Max|Min)/i.test(varName))
|
|
924
939
|
continue;
|
|
925
|
-
// Look at what comes after the operator. If it's a string literal, null,
|
|
926
|
-
// or a number — this is an emptiness/type check, not a
|
|
940
|
+
// Look at what comes after the operator. If it's a string/template literal, null,
|
|
941
|
+
// undefined, true/false, or a number — this is an emptiness/type check, not a
|
|
942
|
+
// secret comparison. The earlier shape only matched empty literals (`''`/`""`),
|
|
943
|
+
// missing the common `typeof x === "object"` / `=== "string"` shapes.
|
|
927
944
|
const afterOp = code.substring(match.index + match[0].length).trimStart();
|
|
928
|
-
if (/^(?:''|""
|
|
945
|
+
if (/^(?:'[^']*'|"[^"]*"|`[^`]*`|null\b|undefined\b|\d|0x|true\b|false\b)/.test(afterOp))
|
|
946
|
+
continue;
|
|
947
|
+
// Client-side React code is not exposed to remote timing attacks: the comparison
|
|
948
|
+
// runs in the user's own browser, where the attacker already has full control of
|
|
949
|
+
// execution timing (network jitter doesn't help them, and a same-machine attacker
|
|
950
|
+
// has easier paths than timing). Skip when the file is a client component.
|
|
951
|
+
const isClientFile = /^['"]use client['"]/.test(code.trimStart()) ||
|
|
952
|
+
/\b(?:useState|useEffect|useReducer|useRef|useMemo|useCallback|useContext|useTransition|useSyncExternalStore|useLayoutEffect)\s*\(/.test(code);
|
|
953
|
+
if (isClientFile)
|
|
929
954
|
continue;
|
|
930
955
|
}
|
|
931
956
|
// Skip VG1005 (.or() filter injection) when all interpolated variables are
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.10",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
5
|
"description": "Security MCP for vibe coding. 390 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis, +25 AI-native rules (MCP supply-chain, RAG/vector poisoning, agent loop DoS, public-prefix LLM keys, sandbox bypass). Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
|
|
6
6
|
"type": "module",
|