const { openDb, UPLOADS_DIR } = require("./db");
const app = express();
const db = openDb();
const PORT = process.env.PORT || 3000;JS 1.0 · Project evidence
JS Todo App (SQLite 4): Find Vulnerabilities
Todo app with SQLite and file attachments. Scaffolded from Cursor's Auto MAX mode.
Project purpose and scale
JS Todo App (SQLite 4): Find Vulnerabilities
Todo app with SQLite and file attachments. Scaffolded from Cursor's Auto MAX mode.
- Source files
- 8
- Reference findings
- 7
- Matched signatures
- 13
- Unmatched signatures
- 43
Reference context
Deterministic reference findings
These findings define the project reference set for JS 1.0. Their presence does not establish exhaustive ground truth.
| Class | Severity | Location | Reference ID |
|---|---|---|---|
| information-exposure | medium | server.js:7 | js-xpowered-by-header-4 |
| allocation-of-resources-without-limits-or-throttling | medium | server.js:106 | js-alloc-without-limits-4a |
| allocation-of-resources-without-limits-or-throttling | medium | server.js:168 | js-alloc-without-limits-4b |
| allocation-of-resources-without-limits-or-throttling | medium | server.js:183 | js-alloc-without-limits-4c |
| path-traversal | high | server.js:140 | js-path-traversal-4a |
| path-traversal | high | server.js:146 | js-path-traversal-4b |
| path-traversal | high | server.js:174 | js-path-traversal-4c |
Finding evidence
Reference-matched normalized signatures
Matched means agreement with the Snyk Code reference set, not independent ground-truth adjudication.
Severity and description show the deterministic most-frequent metadata variant; the final column records how many variants were observed across repetitions.
| Configuration | Class | Representative severity | Location | Recurrence | Representative description | Metadata variants |
|---|---|---|---|---|---|---|
| Claude Opus 4.6 High | allocation-of-resources-without-limits-or-throttling | medium | server.js:106 | 5 of 5 | Expensive operation (a file system operation) is performed by an endpoint handler which does not use a rate-limiting mechanism. It may enable the attackers to perform Denial-of-service attacks. Consider using a rate-limiting middleware such as express-limit. | 1 |
| Claude Opus 4.6 High | information-exposure | medium | server.js:7 | 5 of 5 | Disable X-Powered-By header for your Express app (consider using Helmet middleware), because it exposes information about the used framework to potential attackers. | 1 |
| Claude Opus 4.6 Medium | allocation-of-resources-without-limits-or-throttling | medium | server.js:106 | 5 of 5 | Expensive operation (a file system operation) is performed by an endpoint handler which does not use a rate-limiting mechanism. It may enable the attackers to perform Denial-of-service attacks. Consider using a rate-limiting middleware such as express-limit. | 1 |
| Claude Opus 4.6 Medium | information-exposure | medium | server.js:7 | 5 of 5 | Disable X-Powered-By header for your Express app (consider using Helmet middleware), because it exposes information about the used framework to potential attackers. | 1 |
| Claude Opus 4.7 Max | allocation-of-resources-without-limits-or-throttling | medium | server.js:106 | 5 of 5 | Expensive operation (a file system operation) is performed by an endpoint handler which does not use a rate-limiting mechanism. It may enable the attackers to perform Denial-of-service attacks. Consider using a rate-limiting middleware such as express-limit. | 1 |
| Claude Opus 4.7 Max | allocation-of-resources-without-limits-or-throttling | medium | server.js:168 | 1 of 5 | Expensive operation (a file system operation) is performed by an endpoint handler which does not use a rate-limiting mechanism. It may enable the attackers to perform Denial-of-service attacks. Consider using a rate-limiting middleware such as express-limit. | 1 |
| Claude Opus 4.7 Max | information-exposure | medium | server.js:7 | 5 of 5 | Disable X-Powered-By header for your Express app (consider using Helmet middleware), because it exposes information about the used framework to potential attackers. | 1 |
| Claude Opus 4.7 Max | path-traversal | high | server.js:140 | 1 of 5 | Unsanitized input from an HTTP parameter flows into fs.unlink, where it is used as a path. This may result in a Path Traversal vulnerability and allow an attacker to delete arbitrary files. | 1 |
| Claude Sonnet 4.6 High | allocation-of-resources-without-limits-or-throttling | medium | server.js:106 | 5 of 5 | Expensive operation (a file system operation) is performed by an endpoint handler which does not use a rate-limiting mechanism. It may enable the attackers to perform Denial-of-service attacks. Consider using a rate-limiting middleware such as express-limit. | 1 |
| Claude Sonnet 4.6 High | information-exposure | medium | server.js:7 | 5 of 5 | Disable X-Powered-By header for your Express app (consider using Helmet middleware), because it exposes information about the used framework to potential attackers. | 1 |
| Claude Sonnet 4.6 Medium | allocation-of-resources-without-limits-or-throttling | medium | server.js:106 | 5 of 5 | Expensive operation (a file system operation) is performed by an endpoint handler which does not use a rate-limiting mechanism. It may enable the attackers to perform Denial-of-service attacks. Consider using a rate-limiting middleware such as express-limit. | 1 |
| Claude Sonnet 4.6 Medium | allocation-of-resources-without-limits-or-throttling | medium | server.js:168 | 2 of 5 | Expensive operation (a file system operation) is performed by an endpoint handler which does not use a rate-limiting mechanism. It may enable the attackers to perform Denial-of-service attacks. Consider using a rate-limiting middleware such as express-limit. | 1 |
| Claude Sonnet 4.6 Medium | information-exposure | medium | server.js:7 | 5 of 5 | Disable X-Powered-By header for your Express app (consider using Helmet middleware), because it exposes information about the used framework to potential attackers. | 1 |
Finding evidence
Unmatched normalized signatures
Unmatched reports may be false-positive-shaped, adjacent, or likely product-gap candidates.
Severity and description show the deterministic most-frequent metadata variant; the final column records how many variants were observed across repetitions.
| Configuration | Class | Representative severity | Location | Recurrence | Representative description | Metadata variants |
|---|---|---|---|---|---|---|
| Claude Opus 4.6 High | sql-injection | critical | server.js:34 | 5 of 5 | The deleteTodo function concatenates req.params.id directly into a SQL string ('DELETE FROM todos WHERE id = ' + id) without parameterization. The DELETE route (line 174) passes the unvalidated req.params.id string to this function, allowing an attacker to inject arbitrary SQL via the :id URL parameter. | 5 |
| Claude Opus 4.6 Medium | sql-injection | critical | server.js:34 | 5 of 5 | deleteTodo uses string concatenation ('DELETE FROM todos WHERE id = ' + id) instead of parameterized queries, allowing SQL injection via the id parameter. | 5 |
| Claude Opus 4.6 Medium | sql-injection | critical | server.js:174 | 2 of 5 | The DELETE /api/todos/:id route passes req.params.id directly to q.deleteTodo() without validating it as an integer, enabling SQL injection through the string-concatenated query. | 2 |
| Claude Opus 4.7 Max | csrf | medium | server.js:40 | 2 of 5 | No CSRF protection on state-changing endpoints (POST/PUT/DELETE /api/todos). The app uses multipart/form-data which browsers permit cross-origin, and there is no SameSite cookie, origin/referer check, or CSRF token. | 2 |
| Claude Opus 4.7 Max | csrf | medium | server.js:85 | 3 of 5 | No CSRF protection on state-changing endpoints (POST/PUT/DELETE /api/todos). Endpoints accept multipart/form-data and JSON without any token, origin, or SameSite cookie check, allowing cross-site request forgery. | 3 |
| Claude Opus 4.7 Max | idor | high | server.js:64 | 1 of 5 | There is no authentication or authorization on any endpoint. Any client can list, read, modify, delete, and download attachments of any todo by guessing/iterating numeric ids. | 1 |
| Claude Opus 4.7 Max | idor | medium | server.js:73 | 1 of 5 | All endpoints lack any authentication or authorization. Any user can read, modify, delete, or download attachments for any todo by guessing/iterating sequential numeric IDs. | 1 |
| Claude Opus 4.7 Max | idor | medium | server.js:181 | 1 of 5 | There is no authentication or authorization layer. Any client can enumerate todo IDs and read/modify/delete other users' todos or download their attachments via /api/todos/:id and /api/todos/:id/attachment. | 1 |
| Claude Opus 4.7 Max | improper-code-sanitization | low | server.js:14 | 1 of 5 | Stored filename uses path.extname(file.originalname) directly. While Date.now-based prefix prevents collisions, the attacker-controlled extension is preserved and could later be served back via res.download with a misleading Content-Type/filename, or be problematic if the uploads directory is ever served statically. | 1 |
| Claude Opus 4.7 Max | improper-code-sanitization | low | server.js:189 | 2 of 5 | res.download is called with row.attachment_original_name (user-controlled at upload time) as the download filename. While Express encodes the Content-Disposition header, the unsanitized original name is round-tripped to clients and may be used in subsequent processing without normalization. | 2 |
| Claude Opus 4.7 Max | improper-type-validation | low | server.js:86 | 1 of 5 | POST /api/todos does not validate the types of req.body fields (title, description, completed) before passing them to the database — non-string types are accepted and stored without proper coercion. | 1 |
| Claude Opus 4.7 Max | improper-type-validation | low | server.js:107 | 5 of 5 | PUT /api/todos/:id does not validate that req.params.id is an integer; while the prepared statements are parameterized, lack of validation is inconsistent with other handlers and could mask logic errors. | 5 |
| Claude Opus 4.7 Max | improper-type-validation | high | server.js:167 | 3 of 5 | DELETE /api/todos/:id does not validate that req.params.id is an integer (unlike GET handler at line 74). Combined with the concatenated SQL in deleteTodo, this enables SQL injection. | 3 |
| Claude Opus 4.7 Max | information-exposure | low | server.js:189 | 1 of 5 | res.download uses attachment_original_name (attacker-controlled at upload time) directly in the Content-Disposition header. Express sanitizes the header, but the original filename is fully attacker-controlled and reflected to other users who download the attachment, enabling filename spoofing/social engineering. | 1 |
| Claude Opus 4.7 Max | insecure-transport | low | server.js:195 | 1 of 5 | Server listens over plain HTTP with no TLS, HSTS, or secure-cookie configuration; attachments and todo data traverse the network in cleartext. | 1 |
| Claude Opus 4.7 Max | other | high | package.json:14 | 1 of 5 | Vulnerable dependency: multer ^1.4.5-lts.1 has known DoS / unbounded-resource-consumption advisories (e.g., GHSA-g5hg-p3ph-g8qg, GHSA-fjgf-rc76-4x9p). Should be upgraded to multer 2.x. | 1 |
| Claude Opus 4.7 Max | other | medium | server.js:11 | 1 of 5 | Unrestricted file upload type: multer storage has no fileFilter and no MIME/extension allowlist. Attackers can upload arbitrary file types (HTML, SVG, executables). Because path.extname(file.originalname) is preserved in the stored filename and originalname is returned in res.download Content-Disposition, this can facilitate further attacks (e.g., serving HTML/SVG with XSS if served as static, or distributing malware). | 1 |
| Claude Opus 4.7 Max | other | medium | server.js:64 | 1 of 5 | No authentication or authorization on any endpoint. All todos and attachments are globally readable, modifiable, and deletable by any client, constituting broken access control. | 1 |
| Claude Opus 4.7 Max | sql-injection | critical | server.js:34 | 5 of 5 | deleteTodo builds the DELETE query via string concatenation with req.params.id (used at line 174 without integer validation), allowing arbitrary SQL injection via the DELETE /api/todos/:id endpoint. | 5 |
| Claude Sonnet 4.6 High | csrf | low | server.js | 3 of 5 | All state-mutating endpoints (POST /api/todos, PUT /api/todos/:id, DELETE /api/todos/:id) lack CSRF tokens or SameSite cookie enforcement. If a session/cookie-based authentication mechanism is added in the future, all these endpoints will be immediately exploitable via cross-site request forgery. | 3 |
| Claude Sonnet 4.6 High | idor | high | server.js | 2 of 5 | There is no authentication or authorization layer. Any unauthenticated client can enumerate, read, update, or delete any todo record by guessing sequential integer IDs, constituting an Insecure Direct Object Reference vulnerability. | 2 |
| Claude Sonnet 4.6 High | idor | high | server.js:64 | 1 of 5 | No authentication or authorization is implemented on any API endpoint. Any unauthenticated client can enumerate, read, create, update, or delete all todos by simply guessing or iterating numeric IDs, constituting a classic Insecure Direct Object Reference vulnerability. | 1 |
| Claude Sonnet 4.6 High | improper-type-validation | high | server.js:107 | 3 of 5 | The PUT /api/todos/:id handler reads `req.params.id` without validating that it is an integer (unlike the GET handlers at lines 74 and 182 which call `Number.isInteger`). A non-numeric or crafted string reaches both the SQL-injection-vulnerable `deleteTodo` path and the dynamic UPDATE statement, enabling injection and unexpected behaviour. | 3 |
| Claude Sonnet 4.6 High | improper-type-validation | high | server.js:167 | 4 of 5 | The DELETE /api/todos/:id handler assigns `id = req.params.id` without validating it is an integer (unlike the GET handlers at lines 74–75 which call Number.isInteger). This non-validated string is passed directly to the SQL-injection-vulnerable `deleteTodo` function. | 4 |
| Claude Sonnet 4.6 High | other | low | server.js | 1 of 5 | No HTTP security headers are set (no Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, or Strict-Transport-Security). This leaves the application exposed to clickjacking, MIME-sniffing, and XSS escalation attacks. | 1 |
| Claude Sonnet 4.6 High | other | medium | server.js:11 | 1 of 5 | The multer configuration has no fileFilter callback to restrict allowed file types or MIME types. Attackers can upload executable scripts, malware, or other dangerous file types. While files are served with Content-Disposition: attachment, clients who open them locally are at risk. | 1 |
| Claude Sonnet 4.6 High | other | medium | server.js:18 | 4 of 5 | Multer has no `fileFilter` configured, so any file type (executables, HTML files with scripts, SVGs, etc.) can be uploaded and stored on the server. While files are not directly served statically from the uploads directory, this widens the attack surface, especially if the upload directory is ever misconfigured or future code serves those files. | 4 |
| Claude Sonnet 4.6 High | sql-injection | critical | server.js:34 | 5 of 5 | The deleteTodo function builds a DELETE query by directly concatenating the user-supplied `id` parameter into the SQL string (`"DELETE FROM todos WHERE id = " + id`). Every other query in the file uses parameterized placeholders (`?`), but this one does not, allowing an attacker to inject arbitrary SQL via the route parameter (e.g. `DELETE /api/todos/1 OR 1=1`). | 5 |
| Claude Sonnet 4.6 Medium | csrf | high | server.js | 3 of 5 | All mutating endpoints (POST /api/todos, PUT /api/todos/:id, DELETE /api/todos/:id) lack any CSRF protection. There is no CSRF token validation, no SameSite cookie policy, and no CORS restriction, enabling cross-site request forgery attacks from any origin. | 3 |
| Claude Sonnet 4.6 Medium | csrf | medium | server.js:40 | 1 of 5 | No CSRF protection is implemented. The API accepts state-changing requests (POST, PUT, DELETE) without any CSRF token validation. Combined with the lack of authentication, cross-origin requests from malicious pages can manipulate todos on behalf of any user sharing the server. | 1 |
| Claude Sonnet 4.6 Medium | csrf | medium | server.js:85 | 1 of 5 | State-changing API endpoints (POST /api/todos, PUT /api/todos/:id, DELETE /api/todos/:id) have no CSRF protection—no CSRF tokens, no Origin/Referer header validation, and no SameSite cookie policy. A malicious third-party site can trigger cross-site requests on behalf of authenticated users. | 1 |
| Claude Sonnet 4.6 Medium | idor | medium | server.js | 1 of 5 | There is no authentication or authorization mechanism. Any unauthenticated user can read, create, modify, or delete any todo record by guessing or enumerating integer IDs, constituting an Insecure Direct Object Reference vulnerability. | 1 |
| Claude Sonnet 4.6 Medium | idor | high | server.js:64 | 1 of 5 | No authentication or authorization is implemented on any API endpoint. Any unauthenticated user can list, view, create, update, or delete any todo item and download any attachment. All resource IDs are enumerable sequential integers. | 1 |
| Claude Sonnet 4.6 Medium | idor | high | server.js:166 | 1 of 5 | There is no authentication or authorization mechanism anywhere in the application. Any unauthenticated user can read, create, modify, or delete any todo item by iterating or guessing numeric IDs via the REST API (GET/PUT/DELETE /api/todos/:id). | 1 |
| Claude Sonnet 4.6 Medium | improper-type-validation | high | server.js:107 | 1 of 5 | The PUT /api/todos/:id endpoint does not validate or convert the id parameter to an integer (unlike the GET endpoint at line 74), passing an unvalidated string to multiple database queries and enabling potential SQL injection and IDOR. | 1 |
| Claude Sonnet 4.6 Medium | improper-type-validation | critical | server.js:167 | 1 of 5 | The DELETE /api/todos/:id endpoint reads req.params.id as a raw string with no integer validation and passes it directly to the string-concatenating deleteTodo() function, enabling SQL injection. | 1 |
| Claude Sonnet 4.6 Medium | insecure-transport | low | server.js:195 | 2 of 5 | The server listens on plain HTTP with no HTTPS enforcement or redirect. All data, including any future authentication credentials, is transmitted in cleartext. | 2 |
| Claude Sonnet 4.6 Medium | other | high | server.js | 1 of 5 | No authentication or authorization is implemented on any endpoint. Any unauthenticated user can read all todos, create/modify/delete any todo, and download all attachments. | 1 |
| Claude Sonnet 4.6 Medium | other | low | server.js:7 | 1 of 5 | No HTTP security headers are set (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Strict-Transport-Security). This exposes the application to clickjacking, MIME-type sniffing attacks, and weakens defenses against XSS. | 1 |
| Claude Sonnet 4.6 Medium | other | high | server.js:11 | 1 of 5 | Multer has no fileFilter configured, so any file type (including .js, .sh, .php, .exe, .html) can be uploaded to the server. If the uploads directory is ever served statically or executed, this enables remote code execution or stored XSS via uploaded files. | 1 |
| Claude Sonnet 4.6 Medium | other | high | server.js:18 | 3 of 5 | Multer has no fileFilter callback and no MIME type or extension restrictions. Any file type can be uploaded (e.g., server-side scripts, HTML, executables), potentially enabling malicious file uploads. | 3 |
| Claude Sonnet 4.6 Medium | other | medium | server.js:189 | 1 of 5 | res.download() is called with the user-supplied attachment_original_name as the download filename, which is set in the Content-Disposition response header. If the filename contains CR/LF characters (\r\n), it can cause HTTP response header injection, allowing an attacker to inject arbitrary headers or split the response. | 1 |
| Claude Sonnet 4.6 Medium | sql-injection | critical | server.js:34 | 5 of 5 | deleteTodo builds the SQL DELETE statement by directly concatenating the user-supplied id parameter (req.params.id, a raw URL string that is never validated as an integer) into the query string: `"DELETE FROM todos WHERE id = " + id`. An attacker can supply a value like `1 OR 1=1` to delete all rows, or craft other SQL expressions to manipulate data. | 5 |
Model behavior
Recurrence by configuration
Stable recurrence measures repeated behavior, not independent validity.
| Configuration | Matched signatures | Matched all five | Unmatched signatures | Unmatched all five |
|---|---|---|---|---|
| Claude Opus 4.6 High | 2 | 2 | 1 | 1 |
| Claude Opus 4.6 Medium | 2 | 2 | 2 | 1 |
| Claude Opus 4.7 Max | 4 | 2 | 16 | 2 |
| Claude Sonnet 4.6 High | 2 | 2 | 9 | 1 |
| Claude Sonnet 4.6 Medium | 3 | 2 | 15 | 1 |
Inspectable source context
Reference locations
Apache-licensed fixture excerpts are shown around declared reference lines. They are evidence, not runnable examples.
});
app.put("/api/todos/:id", upload.single("attachment"), (req, res) => {
const id = req.params.id;
app.delete("/api/todos/:id", (req, res) => {
const id = req.params.id;
try {
const row = q.getStoredAttachmentOnly.get(id);
if (!row) return res.status(404).json({ error: "Not found" });app.get("/api/todos/:id/attachment", (req, res) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) return res.status(400).json({ error: "Invalid id" });
try {
const row = q.getAttachmentForDownload.get(id); fs.unlink(path.join(UPLOADS_DIR, existing.attachment_stored_name), () => {});
}
updates.push("attachment_original_name = ?", "attachment_stored_name = ?");
values.push(req.file.originalname, req.file.filename);
} else if (req.body.removeAttachment === true || req.body.removeAttachment === "true") { fs.unlink(path.join(UPLOADS_DIR, existing.attachment_stored_name), () => {});
}
updates.push("attachment_original_name = ?", "attachment_stored_name = ?");
values.push(null, null);
} fs.unlink(path.join(UPLOADS_DIR, row.attachment_stored_name), () => {});
}
q.deleteTodo(id);
res.status(204).send();
} catch (err) {Interpretation boundary
Inspect divergence before classifying it
Unmatched reports may be false-positive-shaped, adjacent to the release reference scope, or likely valid product-gap candidates. Missed reference findings may expose representative-versus-systematic coverage behavior.
Return to explorer evidence