Skip to content

Test/sast autofix validation py#35

Open
dejarno wants to merge 3 commits into
mainfrom
test/sast-autofix-validation-py
Open

Test/sast autofix validation py#35
dejarno wants to merge 3 commits into
mainfrom
test/sast-autofix-validation-py

Conversation

@dejarno

@dejarno dejarno commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added user lookup API endpoint for searching users by name and role
    • Added user retrieval API endpoint to fetch individual user information
    • Added draft/preview mode support for content management workflows

Review Change Stack

Summary by Aikido

⚠️ Security Issues: 3 Quality Issues: 0 Resolved Issues: 0

🚀 New Features

  • Added preview API enabling draft mode and redirecting to locale pages
  • Added Flask user lookup endpoint performing name and role searches
  • Added Express route with raw SQL query construction using untrusted input

More info

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces three new HTTP endpoints for user data access and draft preview functionality. A Flask app exposes /users to query SQLite by name and role; an Express module wires GET /user to fetch user records with password sanitization; a Next.js handler validates a secret token and enables preview mode for authenticated slug requests. All three construct SQL queries using direct string interpolation with user-provided parameters.

Changes

User Query and Authentication Endpoints

Layer / File(s) Summary
Flask User Lookup Endpoint
scripts/user_lookup.py
New Flask app with /users GET endpoint that accepts name and role query parameters, queries signatures.db using string concatenation SQL, returns matching rows as JSON, and includes a get_db() helper and 0.0.0.0:5000 server entry point.
Express User Route Handler
src/jarno.ts
New Express route handler for GET /user that builds SQL queries via template-string interpolation of userId, validates request parameters, executes queries, sanitizes password fields, handles 404/500 errors, and exports registerUserRoutes() for app wiring.
Next.js Draft Mode Handler
src/sast.ts
New Next.js GET route handler that parses secret, slug, and locale from query parameters, validates the secret against a hardcoded value, fetches page data, enables draft mode on success, and redirects to /{locale}/{slug}.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Three endpoints spring to life today,
User lookups come out to play,
But careful now—those queries flow,
With untrusted strings, beware the SQL foe!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Test/sast autofix validation py' does not clearly describe the actual changes. The changeset introduces three new API route handlers (Flask user lookup, Express user routes, and Next.js draft mode), but the title suggests testing/validation work and uses cryptic phrasing that doesn't convey the main changes. Rename the title to clearly describe the primary changes, such as 'Add user lookup and authentication route handlers' or 'Implement Flask, Express, and Next.js user API endpoints'.
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/sast-autofix-validation-py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Comment thread scripts/user_lookup.py
conn = get_db()
cursor = conn.cursor()

query = "SELECT id, name, email, role FROM users WHERE name LIKE '%" + name + "%' AND role = '" + role + "'"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential SQL injection via string-based query concatenation - critical severity
SQL injection might be possible in these locations, especially if the strings being concatenated are controlled via user input.

Show fix

Remediation: If possible, rebuild the query to use prepared statements or an ORM. If that is not possible, make sure the user input is verified or sanitized. As an added layer of protection, we also recommend installing a WAF that blocks SQL injection attacks.

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aikido ignore

Comment thread src/jarno.ts
// Line 10 - helper that builds the vulnerable query
function buildUserQuery(userId) {
// Vulnerable: string interpolation of untrusted input
return `SELECT * FROM users WHERE id = ${userId}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential SQL injection via string-based query concatenation - critical severity
SQL injection might be possible in these locations, especially if the strings being concatenated are controlled via user input.

Show fix

Remediation: If possible, rebuild the query to use prepared statements or an ORM. Alternatively, use parseInt() to sanitize variables. If that is not possible, make sure the user input is verified or sanitized in another way. To autofix all SQL injection instances in your entire app, install Zen for Node.js.

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AikidoSec ignore: this is a test reason

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Based on your feedback, we ignored this issue because of the following reason:

this is a test reason

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 We were not able to ignore this issue because of the following reason:

Only members of this Aikido workspace can ignore issues.

Comment thread src/jarno.ts

// Line ~50 – main Express route where SAST reports the issue
async function registerUserRoutes(app, db) {
app.get('/user', async (req, res) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The anonymous async route handler passed to app.get is large and contains business logic; extract or give it a descriptive name and document it to improve readability and testability.

Details

✨ AI Reasoning
​The PR adds a new Express route where the callback passed to app.get is an anonymous async arrow function that performs request logging, metric tracking, input validation, builds a SQL query, awaits a DB call, handles success and multiple error paths, and serializes output. This is non-trivial business logic spanning many lines; placing it inline hides intent and makes unit testing harder. The surrounding named function (registerUserRoutes) does not document the inline handler's behavior sufficiently, so extracting or naming it would improve clarity and maintainability.

🔧 How do I fix it?
Extract complex anonymous functions into named functions with descriptive names, or add explanatory comments for their purpose.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aikido ignore: no no no no

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Based on your feedback, we ignored this issue because of the following reason:

no no no no

Comment thread src/sast.ts
Comment on lines +6 to +14

const PREVIEW_SECRET = "preview";

export async function GET(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");

if (secret !== PREVIEW_SECRET) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A timing attack might allow hackers to bruteforce passwords - high severity
An insecure way to compare passwords to user input might allow hackers to bruteforce passwords.

Show fix
Suggested change
const PREVIEW_SECRET = "preview";
export async function GET(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");
if (secret !== PREVIEW_SECRET) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}
import crypto from "node:crypto";
const PREVIEW_SECRET = "preview";
export async function GET(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");
if (!secret || !crypto.timingSafeEqual(crypto.createHash('sha256').update(secret).digest(), crypto.createHash('sha256').update(PREVIEW_SECRET).digest())) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AikidoSec ignore: this is a test reason number two

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 We were not able to ignore this issue because of the following reason:

Only members of this Aikido workspace can ignore issues.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 We were not able to ignore this issue because of the following reason:

Only members of this Aikido workspace can ignore issues.

Comment thread src/sast.ts
Comment on lines +17 to +21
const slug = slugFromParams
? Array.isArray(slugFromParams)
? slugFromParams[0]
: slugFromParams
: "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested ternary used to compute slug (ternary inside ternary). Break into clear if/else or separate assignments for readability.

Show fix
Suggested change
const slug = slugFromParams
? Array.isArray(slugFromParams)
? slugFromParams[0]
: slugFromParams
: "";
let slug = "";
if (slugFromParams) {
if (Array.isArray(slugFromParams)) {
slug = slugFromParams[0];
} else {
slug = slugFromParams;
}
}
Details

✨ AI Reasoning
​A single-line expression was introduced that nests a ternary operator inside another ternary, increasing cognitive load when determining the assigned slug value. Nested inline conditionals make the expression harder to read and maintain, especially when parsing request params and fallbacks.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment thread src/jarno.ts
Comment on lines +32 to +33
function sanitizeOutput(user) {
// pretend this is doing something complex

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment rephrases implementation ('pretend this is doing something complex') without explaining intent. Replace with why the sanitization is necessary or remove it.

Suggested change
function sanitizeOutput(user) {
// pretend this is doing something complex
function sanitizeOutput(user) {
// Remove password field to prevent exposure in API responses
Details

✨ AI Reasoning
​A comment that says the code 'pretend this is doing something complex' conveys no design intent or reason; it merely paraphrases the implementation and can become misleading if code changes.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/jarno.ts (1)

44-47: ⚡ Quick win

Validate the shape of userId before using it.

req.query.userId is an untrusted string (or array, if the same key is repeated in the query string — Express returns an array in that case). Even with parameterized queries, accepting arbitrary shapes can produce confusing 500s from the DB layer. Consider coercing to the expected type (e.g., integer) and rejecting otherwise with 400.

♻️ Proposed validation
-      const userId = req.query.userId;
-      if (!userId) {
-        return badRequest(res, 'Missing userId');
-      }
+      const rawUserId = req.query.userId;
+      if (typeof rawUserId !== 'string' || rawUserId.length === 0) {
+        return badRequest(res, 'Missing or invalid userId');
+      }
+      const userId = Number(rawUserId);
+      if (!Number.isInteger(userId) || userId <= 0) {
+        return badRequest(res, 'userId must be a positive integer');
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jarno.ts` around lines 44 - 47, The code currently trusts
req.query.userId directly; change the validation in src/jarno.ts around the
userId handling so you first ensure req.query.userId is a single string (not an
array) and then coerce/validate it to the expected type (e.g., integer) before
using it; if req.query.userId is an array or fails the integer check (use
parseInt or a /^\d+$/ check and Number.isFinite/Number.isInteger after coercion)
return badRequest(res, 'Invalid userId') instead of proceeding, and use the same
userId variable name so later code that consumes userId continues to work.
scripts/user_lookup.py (1)

18-27: ⚡ Quick win

Connection leaks on exception paths.

If cursor.execute(query) raises (or fetchall() does), the conn.close() on Line 25 is skipped and the SQLite connection is leaked. Over time this can lead to OperationalError: database is locked or file-descriptor exhaustion for the Flask process. Note that the sqlite3.connect(…) context manager will hold connections open, so you need to remember to close it manually or write your own context manager — a bare with sqlite3.connect(...) does not close the connection, it only manages the transaction.

Use try/finally or wrap the connection in contextlib.closing.

♻️ Proposed fix using `contextlib.closing`
+from contextlib import closing
@@
-    conn = get_db()
-    cursor = conn.cursor()
-
-    query = "SELECT id, name, email, role FROM users WHERE name LIKE '%" + name + "%' AND role = '" + role + "'"
-    cursor.execute(query)
-
-    rows = cursor.fetchall()
-    conn.close()
+    with closing(get_db()) as conn:
+        cursor = conn.cursor()
+        cursor.execute(
+            "SELECT id, name, email, role FROM users WHERE name LIKE ? AND role = ?",
+            (f"%{name}%", role),
+        )
+        rows = cursor.fetchall()

    return jsonify([dict(r) for r in rows])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/user_lookup.py` around lines 18 - 27, The DB connection in
scripts/user_lookup.py (created via get_db() and used by
cursor.execute/fetchall) can be leaked if execute or fetchall raises; wrap the
connection and cursor in contextlib.closing (or a try/finally) so conn.close()
is always called: import contextlib, use "with contextlib.closing(get_db()) as
conn:" and "with contextlib.closing(conn.cursor()) as cursor:" around the
query/execute/fetchall and then return jsonify([dict(r) for r in rows]); ensure
you remove the bare conn.close() and instead let the context managers guarantee
closure even on exceptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/user_lookup.py`:
- Around line 30-31: The script currently calls app.run(host="0.0.0.0",
port=5000) in the __main__ block which exposes the dev server on all interfaces;
change this to read the bind address from configuration (e.g., an environment
variable) with a safe default of "127.0.0.1" and only allow "0.0.0.0" when
explicitly configured, and update any documentation/comments to recommend using
a production WSGI server (gunicorn/uWSGI) instead of Flask.run for non-local
deployments; locate the app.run call in the __main__ guard to implement this
change.
- Around line 21-22: The SQL query in scripts/user_lookup.py currently builds a
string using name and role which allows SQL injection; modify the code that
creates and executes the query so it uses DB-API parameter binding (sqlite3 '?'
placeholders) instead of concatenation: build a wildcard pattern for the LIKE
(e.g., pattern = "%" + name + "%") and pass (pattern, role) as parameters to
cursor.execute rather than interpolating into query; optionally escape '%' and
'_' in name and add an ESCAPE '\' clause to the LIKE if you need literal
percent/underscore handling.

In `@src/jarno.ts`:
- Around line 4-7: buildUserQuery currently constructs SQL by interpolating the
untrusted userId (SQL injection); change it to return a parameterized query plus
values (or change its callers) instead of string concatenation: update
buildUserQuery to produce a query string with the proper placeholder syntax for
your driver (e.g. $1 for pg or ? for mysql/sqlite) and return the values array
(or have it return both parts), then update the db.query call site (the place
that currently calls db.query(query)) to call db.query(queryText, values) so the
driver binds parameters safely; ensure you pick the exact placeholder style your
DB client uses and remove any direct interpolation of user input in
buildUserQuery.

In `@src/sast.ts`:
- Around line 32-34: The handler currently returns a 401 when pageData is
missing; change this to a resource error (use 404 for not found, or 400 if you
consider it malformed) by updating the NextResponse.json call that checks
pageData so it returns status: 404 (and adjust the message if desired). Locate
the if (!pageData) branch in src/sast.ts where NextResponse.json is called and
replace the 401 status with 404 to reflect a non-authentication "Invalid slug"
response.
- Around line 7-14: The GET handler uses a hardcoded PREVIEW_SECRET string;
replace this with an environment-backed secret (e.g.,
process.env.PREVIEW_SECRET) and stop using the literal "preview". Update the
module to read the secret once (const PREVIEW_SECRET =
process.env.PREVIEW_SECRET) and ensure the GET function checks that
PREVIEW_SECRET is present (return a 500 or fail-fast if missing) before
comparing it to req.nextUrl.searchParams.get("secret"); avoid defaulting to a
weak fallback and consider using a constant-time comparison if available for
security.
- Around line 23-37: The redirect uses untrusted locale and slug directly which
can enable unsafe or manipulated paths; sanitize and validate both values before
calling redirect(): ensure locale is one of allowed values (compare locale
against a whitelist or DEFAULT_LOCALE) and normalize slug to a safe path segment
(reject or percent-encode any input containing //, backslashes, control chars,
or host-like patterns, and strip leading/trailing slashes), then call
draftMode().enable() and redirect() with the validated/normalized values (update
the code around locale, slug, getPagesData, draftMode(), and redirect() to use
the sanitized variables).

---

Nitpick comments:
In `@scripts/user_lookup.py`:
- Around line 18-27: The DB connection in scripts/user_lookup.py (created via
get_db() and used by cursor.execute/fetchall) can be leaked if execute or
fetchall raises; wrap the connection and cursor in contextlib.closing (or a
try/finally) so conn.close() is always called: import contextlib, use "with
contextlib.closing(get_db()) as conn:" and "with
contextlib.closing(conn.cursor()) as cursor:" around the query/execute/fetchall
and then return jsonify([dict(r) for r in rows]); ensure you remove the bare
conn.close() and instead let the context managers guarantee closure even on
exceptions.

In `@src/jarno.ts`:
- Around line 44-47: The code currently trusts req.query.userId directly; change
the validation in src/jarno.ts around the userId handling so you first ensure
req.query.userId is a single string (not an array) and then coerce/validate it
to the expected type (e.g., integer) before using it; if req.query.userId is an
array or fails the integer check (use parseInt or a /^\d+$/ check and
Number.isFinite/Number.isInteger after coercion) return badRequest(res, 'Invalid
userId') instead of proceeding, and use the same userId variable name so later
code that consumes userId continues to work.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 12d47707-74e7-42fa-99e5-c0e8a5fe99e4

📥 Commits

Reviewing files that changed from the base of the PR and between 61e9d5e and a2446d1.

📒 Files selected for processing (3)
  • scripts/user_lookup.py
  • src/jarno.ts
  • src/sast.ts

Comment thread scripts/user_lookup.py
Comment on lines +21 to +22
query = "SELECT id, name, email, role FROM users WHERE name LIKE '%" + name + "%' AND role = '" + role + "'"
cursor.execute(query)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: SQL injection via string concatenation of name and role.

The query at Line 21 concatenates request.args.get("name") and request.args.get("role") directly into SQL, so ?name=%' OR '1'='1 (or a stacked statement / UNION SELECT password ...) lets a caller exfiltrate arbitrary rows from the users table. This is the same anti-pattern flagged in src/jarno.ts. Both Ruff (S608) and SonarCloud have already flagged this.

Use the DB-API placeholder syntax (? for sqlite3) and pass the values as bound parameters. For LIKE, build the wildcard pattern in Python and bind the whole pattern as a single parameter.

🔒 Proposed parameterized fix
-    query = "SELECT id, name, email, role FROM users WHERE name LIKE '%" + name + "%' AND role = '" + role + "'"
-    cursor.execute(query)
+    query = "SELECT id, name, email, role FROM users WHERE name LIKE ? AND role = ?"
+    cursor.execute(query, (f"%{name}%", role))

Additionally, if name may legitimately contain % or _, consider escaping those LIKE metacharacters before binding (e.g., via name.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_") and an ESCAPE '\' clause), so users can't expand the result set with wildcards.

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[failure] 22-22: Change this code to not construct SQL queries directly from user-controlled data.

See more on https://sonarcloud.io/project/issues?id=dejarno_signature-generator&issues=AZ26DfmfOKy6_YlSTtET&open=AZ26DfmfOKy6_YlSTtET&pullRequest=32

🪛 Ruff (0.15.12)

[error] 21-21: Possible SQL injection vector through string-based query construction

(S608)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/user_lookup.py` around lines 21 - 22, The SQL query in
scripts/user_lookup.py currently builds a string using name and role which
allows SQL injection; modify the code that creates and executes the query so it
uses DB-API parameter binding (sqlite3 '?' placeholders) instead of
concatenation: build a wildcard pattern for the LIKE (e.g., pattern = "%" + name
+ "%") and pass (pattern, role) as parameters to cursor.execute rather than
interpolating into query; optionally escape '%' and '_' in name and add an
ESCAPE '\' clause to the LIKE if you need literal percent/underscore handling.

Comment thread scripts/user_lookup.py
Comment on lines +30 to +31
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid binding to 0.0.0.0 by default; gate it behind config.

app.run(host="0.0.0.0", ...) exposes the dev server on every network interface — flagged by Ruff (S104), ast-grep, and SonarCloud. Combined with the SQL-injection issue above, this materially raises blast radius if the script is ever launched on a machine reachable from an untrusted network. Additionally, Flask.run is the Werkzeug dev server and is not intended for production.

Default the bind address to 127.0.0.1 (or read it from an env var), and use a production WSGI server (gunicorn/uWSGI) for any non-local deployment.

🛡️ Proposed change
-if __name__ == "__main__":
-    app.run(host="0.0.0.0", port=5000)
+if __name__ == "__main__":
+    import os
+    host = os.environ.get("APP_HOST", "127.0.0.1")
+    port = int(os.environ.get("APP_PORT", "5000"))
+    app.run(host=host, port=port)
🧰 Tools
🪛 ast-grep (0.42.1)

[warning] 30-30: Running flask app with host 0.0.0.0 could expose the server publicly.
Context: app.run(host="0.0.0.0", port=5000)
Note: [CWE-668]: Exposure of Resource to Wrong Sphere [OWASP A01:2021]: Broken Access Control [REFERENCES]
https://owasp.org/Top10/A01_2021-Broken_Access_Control

(avoid_app_run_with_bad_host-python)

🪛 GitHub Check: SonarCloud Code Analysis

[failure] 31-31: Avoid binding the application to all network interfaces.

See more on https://sonarcloud.io/project/issues?id=dejarno_signature-generator&issues=AZ26DfmfOKy6_YlSTtES&open=AZ26DfmfOKy6_YlSTtES&pullRequest=32

🪛 Ruff (0.15.12)

[error] 31-31: Possible binding to all interfaces

(S104)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/user_lookup.py` around lines 30 - 31, The script currently calls
app.run(host="0.0.0.0", port=5000) in the __main__ block which exposes the dev
server on all interfaces; change this to read the bind address from
configuration (e.g., an environment variable) with a safe default of "127.0.0.1"
and only allow "0.0.0.0" when explicitly configured, and update any
documentation/comments to recommend using a production WSGI server
(gunicorn/uWSGI) instead of Flask.run for non-local deployments; locate the
app.run call in the __main__ guard to implement this change.

Comment thread src/jarno.ts
Comment on lines +4 to +7
function buildUserQuery(userId) {
// Vulnerable: string interpolation of untrusted input
return `SELECT * FROM users WHERE id = ${userId}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Identify the DB driver in use to pick correct placeholder syntax.
fd -e json -g 'package.json' --exec cat {} \;
rg -nP -C2 '\b(pg|mysql2?|sqlite3|better-sqlite3|knex|pg-promise|sequelize|prisma)\b'
rg -nP -C3 '\bdb\s*=\s*' 
rg -nP -C3 'registerUserRoutes\s*\('

Repository: dejarno/signature-generator

Length of output: 1574


🏁 Script executed:

cat -n src/jarno.ts

Repository: dejarno/signature-generator

Length of output: 2384


Critical: SQL injection via direct interpolation of userId.

buildUserQuery interpolates the untrusted req.query.userId directly into the SQL string (Line 6), and that string is then executed by db.query(query) at Line 52. An attacker can pass userId=1 OR 1=1 (or worse, stacked statements / UNION SELECT) to exfiltrate or modify arbitrary data. The comment on Line 5 explicitly acknowledges this — it must not ship.

Use parameterized queries / placeholders provided by your DB driver instead of string concatenation. The exact placeholder syntax depends on the driver ($1 for pg, ? for mysql2/sqlite3/better-sqlite3); please use whichever matches the one bound to db.

🔒 Proposed fix (pg-style placeholders shown; adjust to match your driver)
-function buildUserQuery(userId) {
-    // Vulnerable: string interpolation of untrusted input
-    return `SELECT * FROM users WHERE id = ${userId}`;
-  }
+function buildUserQuery() {
+    return 'SELECT * FROM users WHERE id = $1';
+  }
-      const query = buildUserQuery(userId);
-      try {
-        const result = await db.query(query);
+      try {
+        const result = await db.query(buildUserQuery(), [userId]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jarno.ts` around lines 4 - 7, buildUserQuery currently constructs SQL by
interpolating the untrusted userId (SQL injection); change it to return a
parameterized query plus values (or change its callers) instead of string
concatenation: update buildUserQuery to produce a query string with the proper
placeholder syntax for your driver (e.g. $1 for pg or ? for mysql/sqlite) and
return the values array (or have it return both parts), then update the db.query
call site (the place that currently calls db.query(query)) to call
db.query(queryText, values) so the driver binds parameters safely; ensure you
pick the exact placeholder style your DB client uses and remove any direct
interpolation of user input in buildUserQuery.

Comment thread src/sast.ts
Comment on lines +7 to +14
const PREVIEW_SECRET = "preview";

export async function GET(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");

if (secret !== PREVIEW_SECRET) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use an environment-backed preview secret instead of a hardcoded token.

PREVIEW_SECRET = "preview" is a weak, static credential in source code. This undermines preview auth and secret rotation.

Proposed fix
-const PREVIEW_SECRET = "preview";
+const PREVIEW_SECRET = process.env.PREVIEW_SECRET;

 export async function GET(req: NextRequest) {
   const secret = req.nextUrl.searchParams.get("secret");

-  if (secret !== PREVIEW_SECRET) {
+  if (!PREVIEW_SECRET) {
+    return NextResponse.json({ error: "Preview secret is not configured" }, { status: 500 });
+  }
+
+  if (!secret || secret !== PREVIEW_SECRET) {
     return NextResponse.json({ error: "Invalid token" }, { status: 401 });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const PREVIEW_SECRET = "preview";
export async function GET(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");
if (secret !== PREVIEW_SECRET) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}
const PREVIEW_SECRET = process.env.PREVIEW_SECRET;
export async function GET(req: NextRequest) {
const secret = req.nextUrl.searchParams.get("secret");
if (!PREVIEW_SECRET) {
return NextResponse.json({ error: "Preview secret is not configured" }, { status: 500 });
}
if (!secret || secret !== PREVIEW_SECRET) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sast.ts` around lines 7 - 14, The GET handler uses a hardcoded
PREVIEW_SECRET string; replace this with an environment-backed secret (e.g.,
process.env.PREVIEW_SECRET) and stop using the literal "preview". Update the
module to read the secret once (const PREVIEW_SECRET =
process.env.PREVIEW_SECRET) and ensure the GET function checks that
PREVIEW_SECRET is present (return a 500 or fail-fast if missing) before
comparing it to req.nextUrl.searchParams.get("secret"); avoid defaulting to a
weak fallback and consider using a constant-time comparison if available for
security.

Comment thread src/sast.ts
Comment on lines +23 to +37
const localeFromParams = req.nextUrl.searchParams.get("locale");
const locale = localeFromParams
? Array.isArray(localeFromParams)
? localeFromParams[0]
: localeFromParams
: DEFAULT_LOCALE;

const pageData = await getPagesData(slug, true, locale);

if (!pageData) {
return NextResponse.json({ message: "Invalid slug" }, { status: 401 });
}

draftMode().enable();
redirect(`/${locale}/${slug}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize and validate redirect path segments before redirect().

locale/slug are untrusted input and are used directly in the redirect URL. This can create unsafe redirect targets (e.g. double-slash host-style paths) and path manipulation.

Proposed fix
   const localeFromParams = req.nextUrl.searchParams.get("locale");
-  const locale = localeFromParams
+  const locale = localeFromParams
     ? Array.isArray(localeFromParams)
       ? localeFromParams[0]
       : localeFromParams
     : DEFAULT_LOCALE;

+  const normalizedLocale = locale.replace(/^\/+|\/+$/g, "");
+  const normalizedSlug = slug.replace(/^\/+/, "");
+
+  // Prefer your existing locale allowlist/source of truth here.
+  const safeLocale = normalizedLocale || DEFAULT_LOCALE;
+
   const pageData = await getPagesData(slug, true, locale);

   if (!pageData) {
     return NextResponse.json({ message: "Invalid slug" }, { status: 401 });
   }

   draftMode().enable();
-  redirect(`/${locale}/${slug}`);
+  redirect(`/${safeLocale}/${normalizedSlug}`);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const localeFromParams = req.nextUrl.searchParams.get("locale");
const locale = localeFromParams
? Array.isArray(localeFromParams)
? localeFromParams[0]
: localeFromParams
: DEFAULT_LOCALE;
const pageData = await getPagesData(slug, true, locale);
if (!pageData) {
return NextResponse.json({ message: "Invalid slug" }, { status: 401 });
}
draftMode().enable();
redirect(`/${locale}/${slug}`);
const localeFromParams = req.nextUrl.searchParams.get("locale");
const locale = localeFromParams
? Array.isArray(localeFromParams)
? localeFromParams[0]
: localeFromParams
: DEFAULT_LOCALE;
const normalizedLocale = locale.replace(/^\/+|\/+$/g, "");
const normalizedSlug = slug.replace(/^\/+/, "");
// Prefer your existing locale allowlist/source of truth here.
const safeLocale = normalizedLocale || DEFAULT_LOCALE;
const pageData = await getPagesData(slug, true, locale);
if (!pageData) {
return NextResponse.json({ message: "Invalid slug" }, { status: 401 });
}
draftMode().enable();
redirect(`/${safeLocale}/${normalizedSlug}`);
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 25-27: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=dejarno_signature-generator&issues=AZ2_oJA8wsciz29i1Zvu&open=AZ2_oJA8wsciz29i1Zvu&pullRequest=32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sast.ts` around lines 23 - 37, The redirect uses untrusted locale and
slug directly which can enable unsafe or manipulated paths; sanitize and
validate both values before calling redirect(): ensure locale is one of allowed
values (compare locale against a whitelist or DEFAULT_LOCALE) and normalize slug
to a safe path segment (reject or percent-encode any input containing //,
backslashes, control chars, or host-like patterns, and strip leading/trailing
slashes), then call draftMode().enable() and redirect() with the
validated/normalized values (update the code around locale, slug, getPagesData,
draftMode(), and redirect() to use the sanitized variables).

Comment thread src/sast.ts
Comment on lines +32 to +34
if (!pageData) {
return NextResponse.json({ message: "Invalid slug" }, { status: 401 });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return a resource error status for invalid slug instead of 401.

Invalid slug is not an authentication failure. Prefer 404 (or 400 if treated as malformed input) to keep API semantics accurate.

Proposed fix
-  if (!pageData) {
-    return NextResponse.json({ message: "Invalid slug" }, { status: 401 });
-  }
+  if (!pageData) {
+    return NextResponse.json({ message: "Invalid slug" }, { status: 404 });
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!pageData) {
return NextResponse.json({ message: "Invalid slug" }, { status: 401 });
}
if (!pageData) {
return NextResponse.json({ message: "Invalid slug" }, { status: 404 });
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sast.ts` around lines 32 - 34, The handler currently returns a 401 when
pageData is missing; change this to a resource error (use 404 for not found, or
400 if you consider it malformed) by updating the NextResponse.json call that
checks pageData so it returns status: 404 (and adjust the message if desired).
Locate the if (!pageData) branch in src/sast.ts where NextResponse.json is
called and replace the 401 status with 404 to reflect a non-authentication
"Invalid slug" response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants