@homenshum/convex-mcp-nodebench 0.9.5 → 0.9.7
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/index.js
CHANGED
|
@@ -41,6 +41,27 @@ function auditAuthorization(convexDir) {
|
|
|
41
41
|
const content = readFileSync(filePath, "utf-8");
|
|
42
42
|
const relativePath = filePath.replace(convexDir, "").replace(/^[\\/]/, "");
|
|
43
43
|
const lines = content.split("\n");
|
|
44
|
+
// Pre-scan: detect local helper functions that wrap getAuthUserId/getUserIdentity
|
|
45
|
+
// Pattern: function getSafeUserId(ctx) { ... getAuthUserId(ctx) ... }
|
|
46
|
+
const authHelperNames = [];
|
|
47
|
+
const helperFuncPattern = /(?:async\s+)?function\s+(\w+)\s*\(/g;
|
|
48
|
+
let hm;
|
|
49
|
+
while ((hm = helperFuncPattern.exec(content)) !== null) {
|
|
50
|
+
const hStart = content.slice(0, hm.index).split("\n").length - 1;
|
|
51
|
+
const hBody = lines.slice(hStart, Math.min(hStart + 30, lines.length)).join("\n");
|
|
52
|
+
if (/getAuthUserId|getUserIdentity|getAuthSessionId/.test(hBody)) {
|
|
53
|
+
authHelperNames.push(hm[1]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Also check arrow function helpers: const getUserId = async (ctx) => { ... }
|
|
57
|
+
const arrowHelperPattern = /(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/g;
|
|
58
|
+
while ((hm = arrowHelperPattern.exec(content)) !== null) {
|
|
59
|
+
const hStart = content.slice(0, hm.index).split("\n").length - 1;
|
|
60
|
+
const hBody = lines.slice(hStart, Math.min(hStart + 20, lines.length)).join("\n");
|
|
61
|
+
if (/getAuthUserId|getUserIdentity|getAuthSessionId/.test(hBody)) {
|
|
62
|
+
authHelperNames.push(hm[1]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
44
65
|
for (let i = 0; i < lines.length; i++) {
|
|
45
66
|
const line = lines[i];
|
|
46
67
|
for (const ft of funcTypes) {
|
|
@@ -69,10 +90,14 @@ function auditAuthorization(convexDir) {
|
|
|
69
90
|
}
|
|
70
91
|
}
|
|
71
92
|
const body = lines.slice(i, endLine).join("\n");
|
|
72
|
-
|
|
93
|
+
// Check for direct auth calls OR calls to local auth helper wrappers
|
|
94
|
+
const hasDirectAuth = /ctx\.auth\.getUserIdentity\s*\(\s*\)/.test(body) ||
|
|
73
95
|
/getUserIdentity/.test(body) ||
|
|
74
96
|
/getAuthUserId/.test(body) ||
|
|
75
97
|
/getAuthSessionId/.test(body);
|
|
98
|
+
const callsAuthHelper = authHelperNames.length > 0 &&
|
|
99
|
+
authHelperNames.some(h => new RegExp(`\\b${h}\\s*\\(`).test(body));
|
|
100
|
+
const hasAuthCheck = hasDirectAuth || callsAuthHelper;
|
|
76
101
|
const hasDbWrite = dbWriteOps.test(body);
|
|
77
102
|
const isSensitiveName = writeSensitive.test(funcName);
|
|
78
103
|
if (hasAuthCheck) {
|
|
@@ -81,12 +106,13 @@ function auditAuthorization(convexDir) {
|
|
|
81
106
|
const identityAssign = body.match(/(?:const|let)\s+(\w+)\s*=\s*await\s+ctx\.auth\.getUserIdentity\s*\(\s*\)/);
|
|
82
107
|
if (identityAssign) {
|
|
83
108
|
const varName = identityAssign[1];
|
|
84
|
-
// Recognize: if (!var), if (var === null), if (var), var &&, var ?, throw on !var
|
|
85
|
-
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|if\\s*\\(\\s*${varName}\\s*\\)|${varName}\\s*&&|${varName}\\s*\\?|throw.*!${varName}|${varName}\\s*!==?\\s*null`).test(body);
|
|
109
|
+
// Recognize: if (!var), if (var === null), if (var), var &&, var ?, throw on !var, comparisons
|
|
110
|
+
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|if\\s*\\(\\s*${varName}\\s*\\)|${varName}\\s*&&|${varName}\\s*\\?|throw.*!${varName}|${varName}\\s*!==?\\s*null|===\\s*${varName}|!==\\s*${varName}`).test(body);
|
|
86
111
|
if (!hasNullCheck) {
|
|
87
112
|
uncheckedIdentity++;
|
|
113
|
+
// Queries can intentionally return different data for auth/unauth — warning not critical
|
|
88
114
|
issues.push({
|
|
89
|
-
severity: "critical",
|
|
115
|
+
severity: ft === "query" ? "warning" : "critical",
|
|
90
116
|
location: `${relativePath}:${i + 1}`,
|
|
91
117
|
functionName: funcName,
|
|
92
118
|
message: `${ft} "${funcName}" calls getUserIdentity() but doesn't check for null. Unauthenticated users will get undefined identity.`,
|
|
@@ -98,12 +124,13 @@ function auditAuthorization(convexDir) {
|
|
|
98
124
|
const authUserAssign = body.match(/(?:const|let)\s+(\w+)\s*=\s*await\s+getAuthUserId\s*\(/);
|
|
99
125
|
if (authUserAssign) {
|
|
100
126
|
const varName = authUserAssign[1];
|
|
101
|
-
// Recognize: if (!var), if (var === null), if (var), var &&, var ?, throw on !var
|
|
102
|
-
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|if\\s*\\(\\s*${varName}\\s*\\)|${varName}\\s*&&|${varName}\\s*\\?|throw.*!${varName}|${varName}\\s*!==?\\s*null`).test(body);
|
|
127
|
+
// Recognize: if (!var), if (var === null), if (var), var &&, var ?, throw on !var, comparisons
|
|
128
|
+
const hasNullCheck = new RegExp(`if\\s*\\(\\s*!${varName}\\b|if\\s*\\(\\s*${varName}\\s*===?\\s*null|if\\s*\\(\\s*${varName}\\s*\\)|${varName}\\s*&&|${varName}\\s*\\?|throw.*!${varName}|${varName}\\s*!==?\\s*null|===\\s*${varName}|!==\\s*${varName}`).test(body);
|
|
103
129
|
if (!hasNullCheck) {
|
|
104
130
|
uncheckedIdentity++;
|
|
131
|
+
// Queries can intentionally return different data for auth/unauth — warning not critical
|
|
105
132
|
issues.push({
|
|
106
|
-
severity: "critical",
|
|
133
|
+
severity: ft === "query" ? "warning" : "critical",
|
|
107
134
|
location: `${relativePath}:${i + 1}`,
|
|
108
135
|
functionName: funcName,
|
|
109
136
|
message: `${ft} "${funcName}" calls getAuthUserId() but doesn't check for null. Unauthenticated users will get null userId.`,
|
|
@@ -76,8 +76,11 @@ function auditFunctions(convexDir) {
|
|
|
76
76
|
if (fn.type === "httpAction")
|
|
77
77
|
continue; // httpActions don't have args/returns validators
|
|
78
78
|
if (!fn.hasArgs) {
|
|
79
|
+
// Public mutations/actions without args validators are a security concern (unvalidated client input)
|
|
80
|
+
// Queries and internal functions: just a best-practice recommendation
|
|
81
|
+
const isSecurity = !fn.isInternal && (fn.type === "mutation" || fn.type === "action");
|
|
79
82
|
issues.push({
|
|
80
|
-
severity: "critical",
|
|
83
|
+
severity: isSecurity ? "critical" : "warning",
|
|
81
84
|
location: `${fn.relativePath}:${fn.line}`,
|
|
82
85
|
functionName: fn.name,
|
|
83
86
|
message: `${fn.type} "${fn.name}" is missing args validator`,
|
|
@@ -94,8 +97,9 @@ function auditFunctions(convexDir) {
|
|
|
94
97
|
});
|
|
95
98
|
}
|
|
96
99
|
if (!fn.hasHandler) {
|
|
100
|
+
// Old shorthand syntax (query(async (ctx) => {})) works fine — just a style recommendation
|
|
97
101
|
issues.push({
|
|
98
|
-
severity: "
|
|
102
|
+
severity: "warning",
|
|
99
103
|
location: `${fn.relativePath}:${fn.line}`,
|
|
100
104
|
functionName: fn.name,
|
|
101
105
|
message: `${fn.type} "${fn.name}" is missing handler property (may be using old syntax)`,
|
|
@@ -284,7 +288,7 @@ export const functionTools = [
|
|
|
284
288
|
const functions = extractFunctions(convexDir);
|
|
285
289
|
// Store audit result
|
|
286
290
|
const db = getDb();
|
|
287
|
-
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "
|
|
291
|
+
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "functions", JSON.stringify(issues), issues.length);
|
|
288
292
|
const critical = issues.filter((i) => i.severity === "critical");
|
|
289
293
|
const warnings = issues.filter((i) => i.severity === "warning");
|
|
290
294
|
// Aggregate issues by category for cleaner output
|
|
@@ -75,7 +75,7 @@ function buildSarif(projectDir, auditTypes, limit) {
|
|
|
75
75
|
tool: {
|
|
76
76
|
driver: {
|
|
77
77
|
name: "convex-mcp-nodebench",
|
|
78
|
-
version: "0.9.
|
|
78
|
+
version: "0.9.7",
|
|
79
79
|
informationUri: "https://www.npmjs.com/package/@homenshum/convex-mcp-nodebench",
|
|
80
80
|
rules: [...rulesMap.values()],
|
|
81
81
|
},
|
|
@@ -133,7 +133,7 @@ export const storageAuditTools = [
|
|
|
133
133
|
}
|
|
134
134
|
const { issues, stats } = auditStorageUsage(convexDir);
|
|
135
135
|
const db = getDb();
|
|
136
|
-
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "
|
|
136
|
+
db.prepare("INSERT INTO audit_results (id, project_dir, audit_type, issues_json, issue_count) VALUES (?, ?, ?, ?, ?)").run(genId("audit"), projectDir, "storage", JSON.stringify(issues), issues.length);
|
|
137
137
|
return {
|
|
138
138
|
summary: {
|
|
139
139
|
...stats,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homenshum/convex-mcp-nodebench",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "Convex-specific MCP server applying NodeBench self-instruct diligence patterns to Convex development. Schema audit, function compliance, deployment gates, persistent gotcha DB, and methodology guidance. Complements Context7 (raw docs) and official Convex MCP (deployment introspection) with structured verification workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|