Sample report

A real report, exactly as delivered.

Below is an actual $248 analysis (Automated code scan + AI architecture review), rendered with the exact same component a customer sees on their own report page. Nothing here was rewritten for this page.

What you're looking at

This report is real and unedited — we ran our pipeline against badapp, a small demo application we built specifically to have problems, so we can show you exactly what a report looks like without putting anyone’s actual code on display. That’s also why six findings below are marked critical: badapp was built to have them.

Your report

Here’s what we found.

Generated Sep 27, 2026 · fa2d9708-2710-4430-8d28-4a36a3ce3d7f

There's a lot working well here, and 46 items stand out as worth attention soon — each one below has a clear next step.

What’s strong

  • Your upload passed our safety checks before any analysis began, so nothing unexpected was in the archive.
  • Every check we set out to run finished, so this covers the code you sent us in full.
  • This review ran against a fixed, recorded set of checks, so the same run can be repeated later and compared.
  • Every item below comes with a concrete next step — they are all known, fixable issues.

Worth your attention

  • 46 items are worth handling soon — they are listed first below.
  • 36 further items are worth tidying up when you have time.
82findings in total
  • 1Info
  • 14Low
  • 21Medium
  • 32High
  • 14Critical
Your checklist0 of 82 items resolved

Findings

82 findingsBack to top
  • CriticalAutomated scan

    A GitHub personal access token is written directly into the code

    Effort: About an hour
    What we found
    A GitHub personal access token — the sort of secret value that unlocks a service — is written straight into your code in badapp/.env (line 4), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/.env:4

    1DATABASE_URL=mysql://root:[secret redacted]@db:3306/badapp
    2AWS_ACCESS_KEY_ID=[secret redacted]
    3AWS_SECRET_ACCESS_KEY=[secret redacted]
    4GITHUB_TOKEN=[secret redacted]
    5JWT_SIGNING_SECRET=[secret redacted]
    6STRIPE_SECRET_KEY=[secret redacted]
    7ADMIN_BYPASS_TOKEN=[secret redacted]

    Issue class: a GitHub personal access token committed into the codebase. Location: badapp/.env:4.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    GitHub Personal Access Token
    **Category:** GitHub
    **Match:** GITHUB_TOKEN=[secret redacted]
    
    ---
    
    Reported:
    **Secret:** REDACTED
    **Match:** REDACTED
    **Rule Id:** github-pat
  • CriticalAutomated scan

    A Stripe API key is written directly into the code

    Effort: About an hour
    What we found
    A Stripe API key — the sort of secret value that unlocks a service — is written straight into your code in badapp/.env (line 6), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/.env:6

    3AWS_SECRET_ACCESS_KEY=[secret redacted]
    4GITHUB_TOKEN=[secret redacted]
    5JWT_SIGNING_SECRET=[secret redacted]
    6STRIPE_SECRET_KEY=[secret redacted]
    7ADMIN_BYPASS_TOKEN=[secret redacted]
    8

    Issue class: a Stripe API key committed into the codebase. Location: badapp/.env:6.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Stripe Secret Key
    **Category:** Stripe
    **Match:** STRIPE_SECRET_KEY=[secret redacted]
    
    ---
    
    Reported:
    **Secret:** REDACTED
    **Match:** REDACTED
    **Rule Id:** stripe-access-token
  • CriticalAutomated scan

    A database password is written directly into the code

    Effort: About an hour
    What we found
    A database password — the sort of secret value that unlocks a service — is written straight into your code in badapp/docker-compose.yml (line 7), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/docker-compose.yml:7

    Issue class: a database password committed into the codebase. Location: badapp/docker-compose.yml:7. Rule identifier: secrets:S6697.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Make sure this MySQL database password gets changed and removed from the code.
  • CriticalAutomated scan

    A GitHub personal access token is written directly into the code

    Effort: About an hour
    What we found
    A GitHub personal access token — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/config.js (line 5), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/src/config.js:5

    2module.exports = {
    3 awsAccessKeyId: "[secret redacted]",
    4 awsSecretAccessKey: "[secret redacted]",
    5 apiToken: "[secret redacted]",
    6 adminPassword: "[secret redacted]",
    7 jwtSecret: "[secret redacted]",
    8};

    Issue class: a GitHub personal access token committed into the codebase. Location: badapp/src/config.js:5.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    GitHub Personal Access Token
    **Category:** GitHub
    **Match:**   apiToken: "[secret redacted]",
    
    ---
    
    Reported:
    **Secret:** REDACTED
    **Match:** REDACTED
    **Rule Id:** github-pat
  • CriticalAutomated scan

    Data from outside is rebuilt into program objects without checks

    Effort: An afternoon
    What we found
    In badapp/worker/requirements.txt, data received from elsewhere is turned back into live program objects without being validated first.
    What it means
    Carefully crafted data can make the app do things its author never intended while it is being unpacked.
    What to do
    Use a plain data format and check the contents against an expected shape before using them, rather than restoring objects directly. This is the kind of thing worth handling first.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-502. Published advisories: CVE-2017-18342. Affected dependency: pyyaml 3.13. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    PyYAML: yaml.load() API could execute arbitrary code
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 5.1
    
    In PyYAML before 5.1, the yaml.load() API could execute arbitrary code if used with untrusted data. The load() function has been deprecated in version 5.1 and the 'UnsafeLoader' has been introduced for backward compatibility with the function.
    
    Suggested remediation:
    5.1
  • CriticalAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    Pyyaml (version 3.13) has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is the kind of thing worth handling first.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-20. Published advisories: CVE-2020-14343. Affected dependency: pyyaml 3.13. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    PyYAML: incomplete fix for CVE-2020-1747
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 5.4
    
    A vulnerability was discovered in the PyYAML library in versions before 5.4, where it is susceptible to arbitrary code execution when it processes untrusted YAML files through the full_load method or with the FullLoader loader. Applications that use the library to process untrusted input may be vulnerable to this flaw. This flaw allows an attacker to execute arbitrary code on the system by abusing the python/object/new constructor. This flaw is due to an incomplete fix for CVE-2020-1747.
    
    Suggested remediation:
    5.4
  • CriticalAutomated scan

    Visitor input is trusted directly in a database query

    Effort: About an hour
    What we found
    In badapp/worker/requirements.txt, text that comes from whoever is using your app is dropped straight into a database instruction instead of being handled safely first.
    What it means
    A determined visitor could craft input that reads or changes data they should never be able to reach.
    What to do
    Use the database's built-in placeholders so anything a visitor types is always treated as data, never as an instruction. This is the kind of thing worth handling first.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-89. Published advisories: CVE-2019-7164. Affected dependency: sqlalchemy 1.2.0. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    python-sqlalchemy: SQL Injection when the order_by parameter can be controlled
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 1.3.0b3
    
    SQLAlchemy through 1.2.17 and 1.3.x through 1.3.0b2 allows SQL Injection via the order_by parameter.
    
    Suggested remediation:
    1.3.0b3
  • CriticalAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/db/migrations/0002_add_payments.sql (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/db/migrations/0002_add_payments.sql:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/db/migrations/0002_add_payments.sql:1. Rule identifier: architecture.security.plaintext-card-data-storage.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The payments table declares card_number, card_cvv and card_expiry as plain text, and paymentService.charge inserts those values directly. Storing CVV after authorization is prohibited under PCI DSS under any circumstances, and storing PAN in cleartext pulls the entire database, its backups and anything with DB credentials into card-data scope. The public-read backups bucket in the Terraform makes the blast radius worse.
    
    Reviewed: badapp/db/migrations/0002_add_payments.sql (lines 1-16) — quoted from the scanned source
    -- Payments. Storing PAN + CVV is the point: an architecture/compliance finding that no
    -- SAST rule will flag, because nothing here is syntactically wrong.
    CREATE TABLE IF NOT EXISTS "payments" (
      "id"           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      "order_id"     uuid NOT NULL REFERENCES "orders"("id"),
      "amount_cents" integer NOT NULL,
      "currency"     varchar(3) NOT NULL DEFAULT 'usd',
      "card_number"  text NOT NULL,
      "card_cvv"     text NOT NULL,
      "card_expiry"  text NOT NULL
    );
    
    CREATE UNIQUE INDEX "payments_order_idx" ON "payments" ("order_id");
    
    ALTER TABLE "orders" ADD COLUMN "paid_at" timestamptz;
    
    Suggested remediation:
    Stop persisting card data: delegate capture to a payment processor and store only an opaque token plus last four digits and brand. Drop card_cvv immediately and plan a migration to remove card_number.
  • CriticalAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/infra/main.tf (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/infra/main.tf:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/infra/main.tf:1. Rule identifier: architecture.security.public-read-object-storage.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    aws_s3_bucket.backups sets acl = "public-read". Given the schema stores plaintext card data and password hashes, a world-readable backup bucket exposes the entire customer dataset to anyone who can enumerate or guess the object keys. There is no block-public-access configuration alongside it.
    
    Reviewed: badapp/infra/main.tf (lines 1-14) — quoted from the scanned source
    resource "aws_s3_bucket" "backups" {
      bucket = "badapp-backups"
      acl    = "public-read"
    }
    
    resource "aws_security_group" "web" {
      ingress {
        from_port   = 0
        to_port     = 65535
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
    
    Suggested remediation:
    Remove the public-read ACL, set the bucket to private, and add an aws_s3_bucket_public_access_block with all four flags enabled plus server-side encryption.
  • CriticalAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/src/app.js (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. This is the kind of thing worth handling first.
    See technical detail

    badapp/src/app.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/app.js:1. Rule identifier: architecture.security.unauthenticated-admin-route.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    app.js mounts adminRoutes at /admin with no auth middleware, and admin.js defines no per-route guard. The routes behind it include a shell-executing /ping and a /refund that writes payment rows. The users table has an is_admin column and the project depends on jsonwebtoken, so an authorization concept exists in the data model but is never enforced in the request path.
    
    Reviewed: badapp/src/app.js (lines 1-15) — quoted from the scanned source
    const express = require("express");
    const publicRoutes = require("./routes/public");
    const userRoutes = require("./routes/users");
    const adminRoutes = require("./routes/admin");
    const { log } = require("./lib/logger");
    
    const app = express();
    
    app.use("/", publicRoutes);
    app.use("/", userRoutes);
    // ARCHITECTURE SMELL: admin surface mounted with no authentication middleware at all.
    app.use("/admin", adminRoutes);
    
    app.listen(3000, () => log("listening on 3000"));
    
    Suggested remediation:
    Introduce an authentication middleware (verify a signed token, load the user, require is_admin) and apply it at the app.use('/admin', ...) mount point before any admin handler is registered.
  • CriticalAI review

    Visitor input is trusted directly in a database query

    Effort: About an hour
    What we found
    In badapp/src/db/queries.js (line 1), text that comes from whoever is using your app is dropped straight into a database instruction instead of being handled safely first.
    What it means
    A determined visitor could craft input that reads or changes data they should never be able to reach.
    What to do
    Use the database's built-in placeholders so anything a visitor types is always treated as data, never as an instruction. This is the kind of thing worth handling first.
    See technical detail

    badapp/src/db/queries.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/db/queries.js:1. Rule identifier: architecture.security.sql-injection-string-concatenation.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    findUser, findOrders and findReviews interpolate their arguments into SQL with + and no escaping, and they are fed directly from req.query.id / req.query.userId by the /user and /orders routes. The connection authenticates as the MySQL root user, so a successful injection has full database privileges including reads of the payments table.
    
    Reviewed: badapp/src/db/queries.js (lines 1-13) — quoted from the scanned source
    const { connect } = require("./client");
    
    // SQL injection: string concatenation straight into the query.
    exports.findUser = (id, cb) =>
      connect().query("SELECT * FROM users WHERE id = '" + id + "'", cb);
    
    exports.findOrders = (userId, cb) =>
      connect().query("SELECT * FROM orders WHERE user_id = '" + userId + "'", cb);
    
    // ARCHITECTURE SMELL: queries a `reviews` table that no migration creates (schema drift).
    exports.findReviews = (productId, cb) =>
      connect().query("SELECT * FROM reviews WHERE product_id = '" + productId + "'", cb);
    
    Suggested remediation:
    Switch every query to parameter placeholders (mysql's query(sql, [params]) form) and forbid string-built SQL in the data layer via review or a lint rule.
  • CriticalAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/routes/admin.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/src/routes/admin.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/routes/admin.js:1. Rule identifier: architecture.security.command-injection-shell-exec.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    exec() runs its argument through a shell, and req.query.host is concatenated directly into it. A host value containing shell metacharacters runs additional commands in the container. Combined with the missing authentication on the /admin router, this is an unauthenticated shell.
    
    Reviewed: badapp/src/routes/admin.js (lines 1-19) — quoted from the scanned source
    const express = require("express");
    const { exec } = require("child_process");
    const paymentService = require("../services/paymentService");
    const router = express.Router();
    
    // NO AUTH ON ANY OF THESE. The architecture finding is the missing boundary, not the syntax.
    
    // shell command built from user input -> command injection
    router.get("/ping", (req, res) =>
      exec("ping -c 1 " + req.query.host, (err, stdout) => res.send(stdout)),
    );
    
    router.post("/refund", (req, res) =>
      paymentService.charge(req.query.orderId, { number: "[secret redacted]", cvv: "123", expiry: "12/30" },
        (e) => res.json({ ok: !e })),
    );
    
    module.exports = router;
    
    Suggested remediation:
    Replace exec with execFile('ping', ['-c','1', host]) and validate host against a strict hostname/IP pattern before use; then put the route behind authentication.
  • CriticalAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/src/routes/users.js (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. This is the kind of thing worth handling first.
    See technical detail

    badapp/src/routes/users.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/routes/users.js:1. Rule identifier: architecture.security.eval-on-user-input.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The /calc handler evaluates req.query.expr with eval() and returns the result. Any caller controls the string that is evaluated in the Node process, which means arbitrary JavaScript execution with the privileges of the app (which, per the Dockerfile, runs as root). This is full remote code execution on the web tier, not an input-validation nit.
    
    Reviewed: badapp/src/routes/users.js (lines 1-16) — quoted from the scanned source
    const express = require("express");
    const queries = require("../db/queries");
    const orderService = require("../services/orderService");
    const router = express.Router();
    
    router.get("/user", (req, res) => queries.findUser(req.query.id, (e, rows) => res.json(rows)));
    
    router.get("/orders", (req, res) =>
      orderService.ordersFor(req.query.userId, (e, rows) => res.json(rows)),
    );
    
    // eval on user input -> code injection
    router.get("/calc", (req, res) => res.send(String(eval(req.query.expr))));
    
    module.exports = router;
    
    Suggested remediation:
    Delete the /calc route. If expression evaluation is genuinely a product requirement, replace eval with a sandboxed, non-Turing-complete expression parser and an allowlisted grammar.
  • CriticalAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/worker/db.py (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is the kind of thing worth handling first.
    See technical detail

    badapp/worker/db.py:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/worker/db.py:1. Rule identifier: architecture.security.command-injection-shell-exec.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    worker/db.py concatenates a path into a shell command with shell=True, and run() calls eval() on its argument. Whatever feeds these functions becomes a code-execution channel into the worker container, which shares the same image and network as the web service.
    
    Reviewed: badapp/worker/db.py (lines 1-32) — quoted from the scanned source
    import os
    import sqlite3
    import subprocess
    
    DSN = os.environ["DATABASE_URL"]
    
    
    def lookup(user_id):
        conn = sqlite3.connect("app.db")
        # SQL injection: query built by string formatting
        return conn.execute("SELECT * FROM users WHERE id = '%s'" % user_id).fetchall()
    
    
    def mark_paid(order_id):
        # ARCHITECTURE SMELL: a second, independent writer to `orders` in another language,
        # with no shared schema ownership and no migration of its own.
        conn = sqlite3.connect("app.db")
        conn.execute("UPDATE orders SET status = 'paid' WHERE id = '%s'" % order_id)
    
    
    def backup(path):
        # command injection: shell=True with interpolated input
        subprocess.call("tar czf backup.tgz " + path, shell=True)
    
    
    def run(code):
        # arbitrary code execution
        return eval(code)
    
    
    API_KEY = "[secret redacted]"
    
    Suggested remediation:
    Replace subprocess.call("tar czf ... " + path, shell=True) with a list-argument call and shell=False, and delete run(); if dynamic behaviour is needed, dispatch through an explicit map of allowed operations.
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/Dockerfile
    **Type:** Dockerfile Security Check
    
    Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.
    Specify at least 1 USER command in Dockerfile with non-root user as argument
    
    Suggested remediation:
    Add 'USER <non root user name>' line to the Dockerfile
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    S3 buckets should block public ACLs on buckets and any objects they contain. By blocking, PUTs with fail if the object has any public ACL a.
    
    No public access block so not blocking public acls
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Enable blocking any PUT calls with a public ACL specified
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    S3 bucket policy should have block public policy to prevent users from putting a policy that enable public access.
    
    No public access block so not blocking public policies
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Prevent policies that allow public access being PUT
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    S3 Buckets should be encrypted to protect the data that is stored within them if access is compromised.
    
    Bucket does not have encryption enabled
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Configure bucket encryption
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    S3 buckets should ignore public ACLs on buckets and any objects they contain. By ignoring rather than blocking, PUT calls with public ACLs will still be applied but the ACL will be ignored.
    
    No public access block so not blocking public acls
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Enable ignoring the application of public ACLs in PUT calls
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    Buckets should not have ACLs that allow public access
    
    Bucket has a public ACL: "public-read"
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Don't use canned ACLs or switch to private acl
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    S3 buckets should restrict public policies for the bucket. By enabling, the restrict_public_buckets, only the bucket owner and AWS Services can access if it has a public policy.
    
    No public access block so not restricting public buckets
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Limit the access to public buckets to only the owner or AWS Services (eg; CloudFront)
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    Security groups provide stateful filtering of ingress and egress network traffic to AWS
    resources. It is recommended that no security group allows unrestricted ingress access to
    remote server administration ports, such as SSH to port 22 and RDP to port 3389.
    
    Security group rule allows ingress from public internet.
    Number	Content
    6	resource "aws_security_group" "web" {
    7	  ingress {
    8	    from_port   = 0
    9	    to_port     = 65535
    10	    protocol    = "tcp"
    11	    cidr_blocks = ["0.0.0.0/0"]
    12	  }
    13	}
    
    Suggested remediation:
    Set a more restrictive CIDR range
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    Encryption using AWS keys provides protection for your S3 buckets. To increase control of the encryption and manage factors like rotation use customer managed keys.
    
    Bucket does not encrypt data with a customer managed key.
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Enable encryption using customer managed keys
  • HighAutomated scan

    A private key is written directly into the code

    Effort: About an hour
    What we found
    A private key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/keys.pem (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/src/keys.pem:1

    1-----BEGIN RSA PRIVATE KEY-----
    2[secret redacted]
    3[secret redacted]
    4-----END RSA PRIVATE KEY-----

    Issue class: a private key committed into the codebase. Location: badapp/src/keys.pem:1.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Asymmetric Private Key
    **Category:** AsymmetricPrivateKey
    **Match:** ----BEGIN RSA PRIVATE KEY-----***********************************************************************************************************************************-----END RSA PRIVATE
    
    ---
    
    Reported:
    **Secret:** REDACTED
    **Match:** REDACTED
    **Rule Id:** private-key
  • HighAutomated scan

    Visitor input reaches a system command

    Effort: About an hour
    What we found
    In badapp/src/routes/admin.js (line 10), input from outside your app is used to build a command that the server then runs.
    What it means
    If someone shapes that input carefully, they may be able to run commands of their own choosing on your server.
    What to do
    Call the underlying program with a fixed list of arguments instead of building one long command string, and allow only values you have checked against a known-good list. This is worth handling soon.
    See technical detail

    badapp/src/routes/admin.js:10

    7
    8// shell command built from user input -> command injection
    9router.get("/ping", (req, res) =>
    10 exec("ping -c 1 " + req.query.host, (err, stdout) => res.send(stdout)),
    11);
    12
    13router.post("/refund", (req, res) =>

    Issue class: a code pattern associated with security or reliability defects. Classified as CWE-78. Location: badapp/src/routes/admin.js:10. Rule identifier: opt.-rules.kr-js-child-process-shell-injection.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Result message:** child_process.exec()/execSync() run their argument through a shell. Passing a template literal or concatenated string builds a command-injection sink. Use execFile()/spawn() with an argument array instead, and never interpolate untrusted input into a shell string.
    
    **Snippet:**
    ```  exec("ping -c 1 " + req.query.host, (err, stdout) => res.send(stdout)),```
  • HighAutomated scan

    Something in the code needs a closer look

    Effort: An afternoon
    What we found
    We flagged a pattern in badapp/src/routes/users.js (line 13) that commonly leads to security or reliability problems.
    What it means
    It isn't proof that something is broken today, but patterns like this are where problems tend to start.
    What to do
    Have a developer read through the flagged spot with the technical notes below and either fix it or record why it's fine as it is. This is worth handling soon.
    See technical detail

    badapp/src/routes/users.js:13

    10);
    11
    12// eval on user input -> code injection
    13router.get("/calc", (req, res) => res.send(String(eval(req.query.expr))));
    14
    15module.exports = router;
    16

    Issue class: a code pattern associated with security or reliability defects. Classified as CWE-95. Location: badapp/src/routes/users.js:13. Rule identifier: opt.-rules.kr-js-eval-code-injection.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Result message:** Use of eval() executes its string argument as code. If any part of the input is attacker-influenced this is a code-injection sink. Replace with a safe parser (e.g. JSON.parse) or an explicit dispatch table.
    
    **Snippet:**
    ```router.get("/calc", (req, res) => res.send(String(eval(req.query.expr))));```
  • HighAutomated scan

    Visitor input reaches a system command

    Effort: About an hour
    What we found
    In badapp/worker/db.py (line 23), input from outside your app is used to build a command that the server then runs.
    What it means
    If someone shapes that input carefully, they may be able to run commands of their own choosing on your server.
    What to do
    Call the underlying program with a fixed list of arguments instead of building one long command string, and allow only values you have checked against a known-good list. This is worth handling soon.
    See technical detail

    badapp/worker/db.py:23

    20
    21def backup(path):
    22 # command injection: shell=True with interpolated input
    23 subprocess.call("tar czf backup.tgz " + path, shell=True)
    24
    25
    26def run(code):

    Issue class: a code pattern associated with security or reliability defects. Classified as CWE-78. Location: badapp/worker/db.py:23. Rule identifier: opt.-rules.kr-py-subprocess-shell-true.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Result message:** subprocess called with shell=True runs the command through /bin/sh. Combined with a formatted or concatenated command string this is an OS-command-injection sink. Pass an argument list and shell=False (the default) instead.
    
    **Snippet:**
    ```    subprocess.call("tar czf backup.tgz " + path, shell=True)```
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    Jinja2 (version 2.10) has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Published advisories: CVE-2019-10906. Affected dependency: jinja2 2.10. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    python-jinja2: str.format_map allows sandbox escape
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 2.10.1
    
    In Pallets Jinja before 2.10.1, str.format_map allows a sandbox escape.
    
    Suggested remediation:
    2.10.1
  • HighAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    Jinja2 (version 2.10) has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth handling soon.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-693. Published advisories: CVE-2024-56326. Affected dependency: jinja2 2.10. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    jinja2: Jinja has a sandbox breakout through indirect reference to format method
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 3.1.5
    
    Jinja is an extensible templating engine. Prior to 3.1.5, An oversight in how the Jinja sandboxed environment detects calls to str.format allows an attacker that controls the content of a template to execute arbitrary Python code. To exploit the vulnerability, an attacker needs to control the content of a template. Whether that is the case depends on the type of application using Jinja. This vulnerability impacts users of applications which execute untrusted templates. Jinja's sandbox does catch calls to str.format and ensures they don't escape the sandbox. However, it's possible to store a reference to a malicious string's format method, then pass that to a filter that calls it. No such filters are built-in to Jinja, but could be present through custom filters in an application. After the fix, such indirect calls are also handled by the sandbox. This vulnerability is fixed in 3.1.5.
    
    Suggested remediation:
    3.1.5
  • HighAutomated scan

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/worker/requirements.txt, so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-522. Published advisories: CVE-2018-18074. Affected dependency: requests 2.19.1. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    python-requests: Redirect from HTTPS to HTTP does not remove Authorization header
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 2.20.0
    
    The Requests package before 2.20.0 for Python sends an HTTP Authorization header to an http URI upon receiving a same-hostname https-to-http redirect, which makes it easier for remote attackers to discover credentials by sniffing the network.
    
    Suggested remediation:
    2.20.0
  • HighAutomated scan

    Visitor input is trusted directly in a database query

    Effort: About an hour
    What we found
    In badapp/worker/requirements.txt, text that comes from whoever is using your app is dropped straight into a database instruction instead of being handled safely first.
    What it means
    A determined visitor could craft input that reads or changes data they should never be able to reach.
    What to do
    Use the database's built-in placeholders so anything a visitor types is always treated as data, never as an instruction. This is worth handling soon.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-89. Published advisories: CVE-2019-7548. Affected dependency: sqlalchemy 1.2.0. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    python-sqlalchemy: SQL Injection when the group_by parameter can be controlled
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 1.2.19
    
    SQLAlchemy 1.2.17 has SQL Injection when the group_by parameter can be controlled.
    
    Suggested remediation:
    1.2.19
  • HighAutomated scan

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/config.js (line 6), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/src/config.js:6

    Issue class: maintainability / code quality. Location: badapp/src/config.js:6. Rule identifier: javascript:S2068.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Review this potentially hard-coded password.
  • HighAutomated scan

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/config.js (line 5), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/src/config.js:5

    Issue class: maintainability / code quality. Location: badapp/src/config.js:5. Rule identifier: javascript:S6418.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    "apiToken" detected here, make sure this is not a hard-coded secret.
  • HighAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/src/routes/admin.js (line 10) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. This is worth handling soon.
    See technical detail

    badapp/src/routes/admin.js:10

    Issue class: maintainability / code quality. Location: badapp/src/routes/admin.js:10. Rule identifier: javascript:S4721.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Make sure that executing this OS command is safe here.
  • HighAutomated scan

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/worker/db.py (line 31), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/worker/db.py:31

    Issue class: maintainability / code quality. Location: badapp/worker/db.py:31. Rule identifier: python:S6418.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    "API_KEY" detected here, make sure this is not a hard-coded secret.
  • HighAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/.github/workflows/ci.yml (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. This is worth handling soon.
    See technical detail

    badapp/.github/workflows/ci.yml:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/.github/workflows/ci.yml:1. Rule identifier: architecture.maintainability.no-automated-tests.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The scan reports testFileCount 0 across 24 files, and the CI workflow runs npm install && npm test as its sole step. With no tests present that step either fails or passes vacuously; either way the pipeline provides no signal, and there is nothing preventing regressions in the payment and query paths.
    
    Reviewed: badapp/.github/workflows/ci.yml (lines 1-9) — quoted from the scanned source
    name: ci
    on: [push]
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - run: npm install && npm test
    
    Suggested remediation:
    Add integration tests for the routes and the query layer (starting with /orders and paymentService), and make CI fail on missing tests rather than treating an empty suite as a pass.
  • HighAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/db/migrations/0001_init.sql (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/db/migrations/0001_init.sql:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/db/migrations/0001_init.sql:1. Rule identifier: architecture.maintainability.schema-dialect-mismatch.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The migrations use uuid columns, gen_random_uuid(), timestamptz and quoted identifiers — PostgreSQL syntax — while the application uses the npm mysql driver against a mysql:5.7 container with a mysql:// DSN. These migrations cannot create the schema the running code queries, so the declared data model and the deployed one will diverge from day one.
    
    Reviewed: badapp/db/migrations/0001_init.sql (lines 1-27) — quoted from the scanned source
    -- Core schema. Deliberately stores card data in plaintext (design finding, not a SAST one).
    CREATE TABLE IF NOT EXISTS "users" (
      "id"            uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      "email"         text NOT NULL UNIQUE,
      "password_hash" text NOT NULL,
      "is_admin"      boolean NOT NULL DEFAULT false,
      "created_at"    timestamptz NOT NULL DEFAULT now()
    );
    
    CREATE TABLE IF NOT EXISTS "products" (
      "id"          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      "sku"         varchar(64) NOT NULL UNIQUE,
      "name"        text NOT NULL,
      "price_cents" integer NOT NULL,
      CONSTRAINT "products_price_positive" CHECK ("price_cents" > 0)
    );
    
    CREATE TABLE IF NOT EXISTS "orders" (
      "id"         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
      "user_id"    uuid NOT NULL REFERENCES "users"("id"),
      "product_id" uuid NOT NULL REFERENCES "products"("id"),
      "quantity"   integer NOT NULL DEFAULT 1,
      "status"     text NOT NULL DEFAULT 'pending'
    );
    
    CREATE INDEX "orders_user_idx" ON "orders" ("user_id");
    
    Suggested remediation:
    Pick one engine. If MySQL is the target, rewrite the migrations in MySQL syntax (CHAR(36)/BINARY(16) ids, TIMESTAMP, backtick quoting) and run them in CI against the same image used in compose.
  • HighAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/infra/main.tf (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. This is worth handling soon.
    See technical detail

    badapp/infra/main.tf:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/infra/main.tf:1. Rule identifier: architecture.security.security-group-allows-all-ingress.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    aws_security_group.web has a single ingress rule covering ports 0-65535 from any source address. That exposes any service that happens to bind on the instance — database, admin ports, debug listeners — rather than just the application port, and removes the network layer as a compensating control for the auth gaps above.
    
    Reviewed: badapp/infra/main.tf (lines 1-14) — quoted from the scanned source
    resource "aws_s3_bucket" "backups" {
      bucket = "badapp-backups"
      acl    = "public-read"
    }
    
    resource "aws_security_group" "web" {
      ingress {
        from_port   = 0
        to_port     = 65535
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
    
    Suggested remediation:
    Narrow the ingress rule to the specific application port from the load balancer's security group, and add explicit rules only for ports that must be reachable.
  • HighAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/config.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/src/config.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/config.js:1. Rule identifier: architecture.security.credentials-committed-in-source.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    src/config.js exports awsAccessKeyId, awsSecretAccessKey, apiToken, adminPassword and jwtSecret as literals in a source file, and db/client.js consumes adminPassword as the database password. Whatever the current values are, this design puts credentials in version control and in every image layer, makes rotation a code deploy, and prevents per-environment separation. A source comment asserts the values are decoys; that claim cannot be verified from the code and should not be relied on.
    
    Reviewed: badapp/src/config.js (lines 1-9) — quoted from the scanned source
    // Hardcoded credentials — scanner bait, not real.
    module.exports = {
      awsAccessKeyId: "[secret redacted]",
      awsSecretAccessKey: "[secret redacted]",
      apiToken: "[secret redacted]",
      adminPassword: "[secret redacted]",
      jwtSecret: "[secret redacted]",
    };
    
    Suggested remediation:
    Change config.js to read from process.env (the repo already declares names such as AWS_ACCESS_KEY_ID, JWT_SIGNING_SECRET, DATABASE_URL), fail fast when a variable is missing, and rotate every value that has ever been committed.
  • HighAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/db/client.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/src/db/client.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/db/client.js:1. Rule identifier: architecture.security.database-superuser-in-application.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    db/client.js hardcodes user "root" and passes config.adminPassword as the database password, and docker-compose sets DATABASE_URL to a root DSN. Any SQL injection therefore executes with full DDL and cross-database privileges, and one secret serves two unrelated trust boundaries.
    
    Reviewed: badapp/src/db/client.js (lines 1-12) — quoted from the scanned source
    const mysql = require("mysql");
    const config = require("../config");
    
    // ARCHITECTURE SMELL: a new connection per call, credentials from a checked-in module.
    exports.connect = () =>
      mysql.createConnection({
        host: "db",
        user: "root",
        password: config.adminPassword,
        database: "badapp",
      });
    
    Suggested remediation:
    Create a dedicated application database user with DML-only grants on the badapp schema, give it its own secret supplied via the environment, and stop reusing adminPassword for the datastore.
  • HighAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/db/client.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/src/db/client.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/db/client.js:1. Rule identifier: architecture.performance.connection-per-query-no-pooling.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    client.connect() calls mysql.createConnection on each invocation and no caller ends the connection. Under load this means a TCP handshake plus MySQL auth per request, and connections accumulate until the server's max_connections is hit, at which point all queries start failing. The mysql driver ships a pool API that is going unused.
    
    Reviewed: badapp/src/db/client.js (lines 1-12) — quoted from the scanned source
    const mysql = require("mysql");
    const config = require("../config");
    
    // ARCHITECTURE SMELL: a new connection per call, credentials from a checked-in module.
    exports.connect = () =>
      mysql.createConnection({
        host: "db",
        user: "root",
        password: config.adminPassword,
        database: "badapp",
      });
    
    Suggested remediation:
    Replace createConnection with a single module-level mysql.createPool and have the query layer borrow and release connections, or call pool.query directly.
  • HighAI review

    Text from a visitor is shown on the page without being made safe

    Effort: About an hour
    What we found
    Content supplied by whoever is using your app is rendered back onto the page in badapp/src/routes/public.js (line 1) without being neutralized first.
    What it means
    Someone could sneak instructions into that content and have them run in another person's browser — for example to hijack their signed-in session.
    What to do
    Let your framework escape the value when it renders, and avoid the 'insert raw HTML' escape hatches unless the content is something you generated yourself. This is worth handling soon.
    See technical detail

    badapp/src/routes/public.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/routes/public.js:1. Rule identifier: architecture.security.reflected-xss.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The handler concatenates req.query.name into an HTML response and sends it with Express's default text/html content type, so attacker-supplied markup and script execute in the victim's browser on the application origin. There is no CSP or output-encoding layer anywhere in the app.
    
    Reviewed: badapp/src/routes/public.js (lines 1-10) — quoted from the scanned source
    const express = require("express");
    const router = express.Router();
    
    router.get("/health", (req, res) => res.json({ ok: true }));
    
    // reflected user input -> XSS
    router.get("/hello", (req, res) => res.send("<h1>Hello " + req.query.name + "</h1>"));
    
    module.exports = router;
    
    Suggested remediation:
    HTML-escape the value before interpolation (or render via a template engine with auto-escaping) and add a restrictive Content-Security-Policy header.
  • HighAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/routes/users.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/src/routes/users.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/routes/users.js:1. Rule identifier: architecture.security.missing-authorization-on-data-access.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Both routes take the identifier straight from req.query and return the rows as JSON with no session, no ownership check and no field filtering. findUser does SELECT *, so password_hash and is_admin are serialised to the caller. This is an insecure direct object reference plus over-exposure of sensitive columns.
    
    Reviewed: badapp/src/routes/users.js (lines 1-16) — quoted from the scanned source
    const express = require("express");
    const queries = require("../db/queries");
    const orderService = require("../services/orderService");
    const router = express.Router();
    
    router.get("/user", (req, res) => queries.findUser(req.query.id, (e, rows) => res.json(rows)));
    
    router.get("/orders", (req, res) =>
      orderService.ordersFor(req.query.userId, (e, rows) => res.json(rows)),
    );
    
    // eval on user input -> code injection
    router.get("/calc", (req, res) => res.send(String(eval(req.query.expr))));
    
    module.exports = router;
    
    Suggested remediation:
    Derive the acting user from an authenticated session/token, scope the query to that user (or require an admin role), and project only the columns the client needs instead of SELECT *.
  • HighAI review

    Visitor input is trusted directly in a database query

    Effort: About an hour
    What we found
    In badapp/src/services/paymentService.js (line 1), text that comes from whoever is using your app is dropped straight into a database instruction instead of being handled safely first.
    What it means
    A determined visitor could craft input that reads or changes data they should never be able to reach.
    What to do
    Use the database's built-in placeholders so anything a visitor types is always treated as data, never as an instruction. This is worth handling soon.
    See technical detail

    badapp/src/services/paymentService.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/services/paymentService.js:1. Rule identifier: architecture.security.sql-injection-string-concatenation.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    paymentService.charge builds an INSERT by concatenating orderId and the card number, CVV and expiry into the statement text. Besides the injection risk, string-built SQL containing PAN data tends to end up verbatim in driver error messages and query logs.
    
    Reviewed: badapp/src/services/paymentService.js (lines 1-11) — quoted from the scanned source
    const { connect } = require("../db/client");
    
    // ARCHITECTURE SMELL: a service reaching past the query layer into the driver directly,
    // AND persisting full card data (see 0002_add_payments.sql).
    exports.charge = (orderId, card, cb) =>
      connect().query(
        "INSERT INTO payments (order_id, amount_cents, card_number, card_cvv, card_expiry) VALUES ('" +
          orderId + "', 100, '" + card.number + "', '" + card.cvv + "', '" + card.expiry + "')",
        cb,
      );
    
    Suggested remediation:
    Parameterise the insert, and route it through the shared query layer rather than calling connect().query directly from a service.
  • HighAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/worker/db.py (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/worker/db.py:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/worker/db.py:1. Rule identifier: architecture.security.sql-injection-string-concatenation.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    lookup() and mark_paid() interpolate user_id and order_id into SQL strings with %s formatting. sqlite3 accepts parameters natively, so this is gratuitous injection exposure in a component that mutates order payment status.
    
    Reviewed: badapp/worker/db.py (lines 1-32) — quoted from the scanned source
    import os
    import sqlite3
    import subprocess
    
    DSN = os.environ["DATABASE_URL"]
    
    
    def lookup(user_id):
        conn = sqlite3.connect("app.db")
        # SQL injection: query built by string formatting
        return conn.execute("SELECT * FROM users WHERE id = '%s'" % user_id).fetchall()
    
    
    def mark_paid(order_id):
        # ARCHITECTURE SMELL: a second, independent writer to `orders` in another language,
        # with no shared schema ownership and no migration of its own.
        conn = sqlite3.connect("app.db")
        conn.execute("UPDATE orders SET status = 'paid' WHERE id = '%s'" % order_id)
    
    
    def backup(path):
        # command injection: shell=True with interpolated input
        subprocess.call("tar czf backup.tgz " + path, shell=True)
    
    
    def run(code):
        # arbitrary code execution
        return eval(code)
    
    
    API_KEY = "[secret redacted]"
    
    Suggested remediation:
    Use parameterised execute calls, e.g. conn.execute("SELECT * FROM users WHERE id = ?", (user_id,)), throughout worker/db.py.
  • HighAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/worker/db.py (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth handling soon.
    See technical detail

    badapp/worker/db.py:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/worker/db.py:1. Rule identifier: architecture.maintainability.divergent-datastores-for-same-tables.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    worker/db.py reads DATABASE_URL into DSN but then ignores it and connects to sqlite3 "app.db", updating orders.status to 'paid'. Two services claim ownership of the same logical table in two different databases, and the SQLite file is per-container and ephemeral, so payment status written by the worker is invisible to the web tier and lost on restart.
    
    Reviewed: badapp/worker/db.py (lines 1-32) — quoted from the scanned source
    import os
    import sqlite3
    import subprocess
    
    DSN = os.environ["DATABASE_URL"]
    
    
    def lookup(user_id):
        conn = sqlite3.connect("app.db")
        # SQL injection: query built by string formatting
        return conn.execute("SELECT * FROM users WHERE id = '%s'" % user_id).fetchall()
    
    
    def mark_paid(order_id):
        # ARCHITECTURE SMELL: a second, independent writer to `orders` in another language,
        # with no shared schema ownership and no migration of its own.
        conn = sqlite3.connect("app.db")
        conn.execute("UPDATE orders SET status = 'paid' WHERE id = '%s'" % order_id)
    
    
    def backup(path):
        # command injection: shell=True with interpolated input
        subprocess.call("tar czf backup.tgz " + path, shell=True)
    
    
    def run(code):
        # arbitrary code execution
        return eval(code)
    
    
    API_KEY = "[secret redacted]"
    
    Suggested remediation:
    Make the worker connect using DSN through the same engine as the web service, and give payment-status writes a single owner (either the worker or the app) with a migration that lives in db/migrations.
  • MediumAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth fixing when you next touch this area.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    Versioning in Amazon S3 is a means of keeping multiple variants of an object in the same bucket.
    
    You can use the S3 Versioning feature to preserve, retrieve, and restore every version of every object stored in your buckets.
    
    With versioning you can recover more easily from both unintended user actions and application failures.
    
    Bucket does not have versioning enabled
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Enable versioning to protect against accidental/malicious removal or modification
  • MediumAutomated scan

    Something the app accepts has no size limit

    Effort: An afternoon
    What we found
    The code in badapp/worker/requirements.txt accepts input or allocates resources without an upper bound.
    What it means
    One oversized request could use up memory or processing time and slow the app down for everyone.
    What to do
    Set a sensible maximum and reject anything larger with a clear message. This is worth fixing when you next touch this area.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-400. Published advisories: CVE-2020-28493. Affected dependency: jinja2 2.10. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    python-jinja2: ReDoS vulnerability in the urlize filter
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 2.11.3
    
    This affects the package jinja2 from 0.0.0 and before 2.11.3. The ReDoS vulnerability is mainly due to the `_punctuation_re regex` operator and its use of multiple wildcards. The last wildcard is the most exploitable as it searches for trailing punctuation. This issue can be mitigated by Markdown to format user content instead of the urlize filter, or by implementing request timeouts and limiting process memory.
    
    Suggested remediation:
    2.11.3
  • MediumAutomated scan

    Text from a visitor is shown on the page without being made safe

    Effort: About an hour
    What we found
    Content supplied by whoever is using your app is rendered back onto the page in badapp/worker/requirements.txt without being neutralized first.
    What it means
    Someone could sneak instructions into that content and have them run in another person's browser — for example to hijack their signed-in session.
    What to do
    Let your framework escape the value when it renders, and avoid the 'insert raw HTML' escape hatches unless the content is something you generated yourself. This is worth fixing when you next touch this area.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-79. Published advisories: CVE-2024-22195. Affected dependency: jinja2 2.10. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    jinja2: HTML attribute injection when passing user input as keys to xmlattr filter
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 3.1.3
    
    Jinja is an extensible templating engine. Special placeholders in the template allow writing code similar to Python syntax. It is possible to inject arbitrary HTML attributes into the rendered HTML template, potentially leading to Cross-Site Scripting (XSS). The Jinja `xmlattr` filter can be abused to inject arbitrary HTML attribute keys and values, bypassing the auto escaping mechanism and potentially leading to XSS. It may also be possible to bypass attribute validation checks if they are blacklist-based.
    
    Suggested remediation:
    3.1.3
  • MediumAutomated scan

    Text from a visitor is shown on the page without being made safe

    Effort: About an hour
    What we found
    Content supplied by whoever is using your app is rendered back onto the page in badapp/worker/requirements.txt without being neutralized first.
    What it means
    Someone could sneak instructions into that content and have them run in another person's browser — for example to hijack their signed-in session.
    What to do
    Let your framework escape the value when it renders, and avoid the 'insert raw HTML' escape hatches unless the content is something you generated yourself. This is worth fixing when you next touch this area.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-79. Published advisories: CVE-2024-34064. Affected dependency: jinja2 2.10. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    jinja2: accepts keys containing non-attribute characters
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 3.1.4
    
    Jinja is an extensible templating engine. The `xmlattr` filter in affected versions of Jinja accepts keys containing non-attribute characters. XML/HTML attributes cannot contain spaces, `/`, `>`, or `=`, as each would then be interpreted as starting a separate attribute. If an application accepts keys (as opposed to only values) as user input, and renders these in pages that other users see as well, an attacker could use this to inject other attributes and perform XSS. The fix for CVE-2024-22195 only addressed spaces but not other characters. Accepting keys as user input is now explicitly considered an unintended use case of the `xmlattr` filter, and code that does so without otherwise validating the input should be flagged as insecure, regardless of Jinja version. Accepting _values_ as user input continues to be safe. This vulnerability is fixed in 3.1.4.
    
    Suggested remediation:
    3.1.4
  • MediumAutomated scan

    The app reveals more about itself than it needs to

    Effort: About an hour
    What we found
    Detailed internal information is exposed in badapp/worker/requirements.txt — for example a full error trace or internal identifiers shown to whoever is using the app.
    What it means
    Those details make it much easier for someone to work out how to attack the app, and they can leak information about your customers.
    What to do
    Show a short, friendly message to the person using the app and keep the detailed version in your own logs. This is worth fixing when you next touch this area.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-200. Published advisories: CVE-2023-32681. Affected dependency: requests 2.19.1. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    python-requests: Unintended leak of Proxy-Authorization header
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 2.31.0
    
    Requests is a HTTP library. Since Requests 2.3.0, Requests has been leaking Proxy-Authorization headers to destination servers when redirected to an HTTPS endpoint. This is a product of how we use `rebuild_proxies` to reattach the `Proxy-Authorization` header to requests. For HTTP connections sent through the tunnel, the proxy will identify the header in the request itself and remove it prior to forwarding to the destination server. However when sent over HTTPS, the `Proxy-Authorization` header must be sent in the CONNECT request as the proxy has no visibility into the tunneled request. This results in Requests forwarding proxy credentials to the destination server unintentionally, allowing a malicious actor to potentially exfiltrate sensitive information. This issue has been patched in version 2.31.0.
    
    Suggested remediation:
    2.31.0
  • MediumAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    Requests (version 2.19.1) has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth fixing when you next touch this area.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-670. Published advisories: CVE-2024-35195. Affected dependency: requests 2.19.1. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    requests: subsequent requests to the same host ignore cert verification
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 2.32.0
    
    Requests is a HTTP library. Prior to 2.32.0, when making requests through a Requests `Session`, if the first request is made with `verify=False` to disable cert verification, all subsequent requests to the same host will continue to ignore cert verification regardless of changes to the value of `verify`. This behavior will continue for the lifecycle of the connection in the connection pool. This vulnerability is fixed in 2.32.0.
    
    Suggested remediation:
    2.32.0
  • MediumAutomated scan

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/worker/requirements.txt, so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth fixing when you next touch this area.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-522. Published advisories: CVE-2024-47081. Affected dependency: requests 2.19.1. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    requests: Requests vulnerable to .netrc credentials leak via malicious URLs
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 2.32.4
    
    Requests is a HTTP library. Due to a URL parsing issue, Requests releases prior to 2.32.4 may leak .netrc credentials to third parties for specific maliciously-crafted URLs. Users should upgrade to version 2.32.4 to receive a fix. For older versions of Requests, use of the .netrc file can be disabled with `trust_env=False` on one's Requests Session.
    
    Suggested remediation:
    2.32.4
  • MediumAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    Requests (version 2.19.1) has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is worth fixing when you next touch this area.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-377. Published advisories: CVE-2026-25645. Affected dependency: requests 2.19.1. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    requests: Requests: Security bypass due to predictable temporary file creation
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 2.33.0
    
    Requests is a HTTP library. Prior to version 2.33.0, the `requests.utils.extract_zipped_paths()` utility function uses a predictable filename when extracting files from zip archives into the system temporary directory. If the target file already exists, it is reused without validation. A local attacker with write access to the temp directory could pre-create a malicious file that would be loaded in place of the legitimate one. Standard usage of the Requests library is not affected by this vulnerability. Only applications that call `extract_zipped_paths()` directly are impacted. Starting in version 2.33.0, the library extracts files to a non-deterministic location. If developers are unable to upgrade, they can set `TMPDIR` in their environment to a directory with restricted write access.
    
    Suggested remediation:
    2.33.0
  • MediumAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/Dockerfile (line 1) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. This is worth fixing when you next touch this area.
    See technical detail

    badapp/Dockerfile:1

    Issue class: maintainability / code quality. Location: badapp/Dockerfile:1. Rule identifier: docker:S6471.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The "node" image runs with "root" as the default user. Make sure it is safe here.
  • MediumAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/Dockerfile (line 3) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. This is worth fixing when you next touch this area.
    See technical detail

    badapp/Dockerfile:3

    Issue class: maintainability / code quality. Location: badapp/Dockerfile:3. Rule identifier: docker:S6470.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Copying recursively might inadvertently add sensitive data to the container. Make sure it is safe here.
  • MediumAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/infra/main.tf (line 1) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. This is worth fixing when you next touch this area.
    See technical detail

    badapp/infra/main.tf:1

    Issue class: maintainability / code quality. Location: badapp/infra/main.tf:1. Rule identifier: terraform:S6281.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    No Public Access Block configuration prevents public ACL/policies to be set on this S3 bucket. Make sure it is safe here.
  • MediumAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/infra/main.tf (line 3) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. This is worth fixing when you next touch this area.
    See technical detail

    badapp/infra/main.tf:3

    Issue class: maintainability / code quality. Location: badapp/infra/main.tf:3. Rule identifier: terraform:S6265.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Make sure granting access to "AllUsers" group is safe here.
  • MediumAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/src/routes/users.js (line 13) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. This is worth fixing when you next touch this area.
    See technical detail

    badapp/src/routes/users.js:13

    Issue class: maintainability / code quality. Location: badapp/src/routes/users.js:13. Rule identifier: javascript:S1523.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Make sure that this dynamic injection or execution of code is safe.
  • MediumAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in one of your files, so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth fixing when you next touch this area.
    See technical detail

    Issue class: a structural observation about how the system is designed and assembled. Rule identifier: architecture.security.auth-bypass-env-flag.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The environment inventory includes ADMIN_BYPASS_TOKEN alongside JWT_SIGNING_SECRET, yet no code in the scanned files reads either one and no authentication middleware exists. A named bypass token is a standing backdoor pattern: if any deployed code path honours it, it sidesteps whatever authorization is eventually added.
    
    Suggested remediation:
    Confirm whether anything consumes ADMIN_BYPASS_TOKEN; if so, remove that code path and rely on the normal token [secret redacted] flow, and drop the variable from all environments.
  • MediumAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/.github/workflows/ci.yml (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. This is worth fixing when you next touch this area.
    See technical detail

    badapp/.github/workflows/ci.yml:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/.github/workflows/ci.yml:1. Rule identifier: architecture.maintainability.unpinned-ci-and-nondeterministic-install.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The workflow pins actions/checkout to a major tag and runs npm install, which resolves dependencies afresh and can mutate the lockfile, so CI is not reproducing the versions a deploy would use. The workflow also has no Node version setup step, so the runner's default Node decides the build, which may differ from the node:14 image in the Dockerfile.
    
    Reviewed: badapp/.github/workflows/ci.yml (lines 1-9) — quoted from the scanned source
    name: ci
    on: [push]
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - run: npm install && npm test
    
    Suggested remediation:
    Add actions/setup-node pinned to the same Node major as the image, switch to npm ci, and pin actions to a full commit SHA.
  • MediumAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/docker-compose.yml (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth fixing when you next touch this area.
    See technical detail

    badapp/docker-compose.yml:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/docker-compose.yml:1. Rule identifier: architecture.security.database-port-published-to-host.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The db service maps 3306:3306, which makes the database reachable from outside the compose network rather than only from web and worker. The root password is supplied inline in the compose environment block, so it lands in shell history, process listings and docker inspect output.
    
    Reviewed: badapp/docker-compose.yml (lines 1-17) — quoted from the scanned source
    services:
      web:
        build: .
        ports:
          - "3000:3000"
        environment:
          - DATABASE_URL=mysql://root:[secret redacted]@db:3306/badapp
      db:
        image: mysql:5.7
        ports:
          - "3306:3306"
        environment:
          - MYSQL_ROOT_PASSWORD=[secret redacted]
      worker:
        build: .
        command: python worker/db.py
    
    Suggested remediation:
    Remove the db ports mapping (service-to-service traffic already works over the compose network) and move MYSQL_ROOT_PASSWORD and DATABASE_URL into a secrets mechanism or an untracked env file.
  • MediumAI review

    Visitor input reaches a system command

    Effort: About an hour
    What we found
    In badapp/Dockerfile (line 1), input from outside your app is used to build a command that the server then runs.
    What it means
    If someone shapes that input carefully, they may be able to run commands of their own choosing on your server.
    What to do
    Call the underlying program with a fixed list of arguments instead of building one long command string, and allow only values you have checked against a known-good list. This is worth fixing when you next touch this area.
    See technical detail

    badapp/Dockerfile:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/Dockerfile:1. Rule identifier: architecture.security.container-runs-as-root-with-full-context.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The Dockerfile does COPY . . with no .dockerignore evidence, then runs CMD as the default root user. That bakes local files — including any .env, git metadata and infrastructure code — into the image layers and gives a process that already has command-injection and eval sinks full root capability inside the container.
    
    Reviewed: badapp/Dockerfile (lines 1-8) — quoted from the scanned source
    FROM node:14
    WORKDIR /app
    COPY . .
    RUN npm install
    # Runs as root, ships the whole tree including .env — infra findings.
    EXPOSE 3000
    CMD ["node", "src/app.js"]
    
    Suggested remediation:
    Add a .dockerignore covering .env, .git, infra and node_modules, install with a production-only lockfile install, and add a non-root USER before CMD.
  • MediumAI review

    Visitor input is trusted directly in a database query

    Effort: About an hour
    What we found
    In badapp/src/db/queries.js (line 1), text that comes from whoever is using your app is dropped straight into a database instruction instead of being handled safely first.
    What it means
    A determined visitor could craft input that reads or changes data they should never be able to reach.
    What to do
    Use the database's built-in placeholders so anything a visitor types is always treated as data, never as an instruction. This is worth fixing when you next touch this area.
    See technical detail

    badapp/src/db/queries.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/db/queries.js:1. Rule identifier: architecture.maintainability.schema-drift-missing-table.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    The data layer exposes a query against reviews, but the migration set defines only users, products, orders and payments. Any caller of findReviews will fail at runtime with an unknown-table error, and the mismatch shows migrations are not the authoritative source for the schema the code assumes.
    
    Reviewed: badapp/src/db/queries.js (lines 1-13) — quoted from the scanned source
    const { connect } = require("./client");
    
    // SQL injection: string concatenation straight into the query.
    exports.findUser = (id, cb) =>
      connect().query("SELECT * FROM users WHERE id = '" + id + "'", cb);
    
    exports.findOrders = (userId, cb) =>
      connect().query("SELECT * FROM orders WHERE user_id = '" + userId + "'", cb);
    
    // ARCHITECTURE SMELL: queries a `reviews` table that no migration creates (schema drift).
    exports.findReviews = (productId, cb) =>
      connect().query("SELECT * FROM reviews WHERE product_id = '" + productId + "'", cb);
    
    Suggested remediation:
    Either add a migration creating reviews or delete findReviews, and add a CI step that applies migrations to a scratch database and exercises the query layer against it.
  • MediumAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/routes/admin.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. This is worth fixing when you next touch this area.
    See technical detail

    badapp/src/routes/admin.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/routes/admin.js:1. Rule identifier: architecture.maintainability.hardcoded-business-values-in-handler.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    POST /refund passes a literal card object into paymentService.charge, and charge itself inserts amount_cents as the constant 100 regardless of the order. The endpoint is also named refund while performing an insert into payments. Financial logic embedded as literals in a route handler is impossible to test or configure and will silently produce wrong ledger rows.
    
    Reviewed: badapp/src/routes/admin.js (lines 1-19) — quoted from the scanned source
    const express = require("express");
    const { exec } = require("child_process");
    const paymentService = require("../services/paymentService");
    const router = express.Router();
    
    // NO AUTH ON ANY OF THESE. The architecture finding is the missing boundary, not the syntax.
    
    // shell command built from user input -> command injection
    router.get("/ping", (req, res) =>
      exec("ping -c 1 " + req.query.host, (err, stdout) => res.send(stdout)),
    );
    
    router.post("/refund", (req, res) =>
      paymentService.charge(req.query.orderId, { number: "[secret redacted]", cvv: "123", expiry: "12/30" },
        (e) => res.json({ ok: !e })),
    );
    
    module.exports = router;
    
    Suggested remediation:
    Derive the amount from the order record, pass card or token data in as a parameter rather than a literal, and rename the operation to match what it actually does.
  • MediumAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/src/routes/users.js (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. This is worth fixing when you next touch this area.
    See technical detail

    badapp/src/routes/users.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/routes/users.js:1. Rule identifier: architecture.maintainability.errors-discarded-in-callbacks.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Handlers such as /user and /orders bind the error as e and never inspect it, sending res.json(rows) with rows undefined on failure, which yields a 200 with an empty body. /refund collapses every failure mode into ok: false with no detail or logging. Debugging a production incident with this pattern requires reproducing it locally.
    
    Reviewed: badapp/src/routes/users.js (lines 1-16) — quoted from the scanned source
    const express = require("express");
    const queries = require("../db/queries");
    const orderService = require("../services/orderService");
    const router = express.Router();
    
    router.get("/user", (req, res) => queries.findUser(req.query.id, (e, rows) => res.json(rows)));
    
    router.get("/orders", (req, res) =>
      orderService.ordersFor(req.query.userId, (e, rows) => res.json(rows)),
    );
    
    // eval on user input -> code injection
    router.get("/calc", (req, res) => res.send(String(eval(req.query.expr))));
    
    module.exports = router;
    
    Suggested remediation:
    Add a shared error-handling path: check err in each callback, log it with context, and respond with an appropriate status code via Express's next(err) and a single error middleware.
  • MediumAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/src/services/paymentService.js (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. This is worth fixing when you next touch this area.
    See technical detail

    badapp/src/services/paymentService.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/services/paymentService.js:1. Rule identifier: architecture.maintainability.layering-violation-service-bypasses-data-layer.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    There is a db/queries module that owns SQL, but paymentService imports connect from db/client and issues its own INSERT. With two places allowed to write SQL, cross-cutting fixes — parameterisation, pooling, redaction of card fields in logs — have to be applied in more than one location and will drift.
    
    Reviewed: badapp/src/services/paymentService.js (lines 1-11) — quoted from the scanned source
    const { connect } = require("../db/client");
    
    // ARCHITECTURE SMELL: a service reaching past the query layer into the driver directly,
    // AND persisting full card data (see 0002_add_payments.sql).
    exports.charge = (orderId, card, cb) =>
      connect().query(
        "INSERT INTO payments (order_id, amount_cents, card_number, card_cvv, card_expiry) VALUES ('" +
          orderId + "', 100, '" + card.number + "', '" + card.cvv + "', '" + card.expiry + "')",
        cb,
      );
    
    Suggested remediation:
    Move the payment insert into db/queries as a named, parameterised function and have paymentService depend only on that module.
  • LowAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. There's no rush on this one.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/Dockerfile
    **Type:** Dockerfile Security Check
    
    You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers.
    Add HEALTHCHECK instruction in your Dockerfile
    
    Suggested remediation:
    Add HEALTHCHECK instruction in Dockerfile
  • LowAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. There's no rush on this one.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    Ensures S3 bucket logging is enabled for S3 buckets
    Bucket has logging disabled
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Add a logging block to the resource to enable access logging
  • LowAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. There's no rush on this one.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    The "block public access" settings in S3 override individual policies that apply to a given bucket, meaning that all public access can be controlled in one central types for that bucket. It is therefore good practice to define these settings for each bucket in order to clearly define the public access that can be allowed for it.
    
    Bucket does not have a corresponding public access block.
    Number	Content
    1	resource "aws_s3_bucket" "backups" {
    2	  bucket = "badapp-backups"
    3	  acl    = "public-read"
    4	}
    
    Suggested remediation:
    Define a aws_s3_bucket_public_access_block for the given bucket to control public access policies
  • LowAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. There's no rush on this one.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    Security groups should include a description for auditing purposes.
    
    Simplifies auditing, debugging, and managing security groups.
    
    Security group explicitly uses the default description.
    Number	Content
    6	resource "aws_security_group" "web" {
    7	  ingress {
    8	    from_port   = 0
    9	    to_port     = 65535
    10	    protocol    = "tcp"
    11	    cidr_blocks = ["0.0.0.0/0"]
    12	  }
    13	}
    
    Suggested remediation:
    Add descriptions for all security groups
  • LowAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    One of the outside packages your app relies on has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. There's no rush on this one.
    See technical detail

    Issue class: a third-party dependency with a published advisory.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    **Target:** badapp/infra/main.tf
    **Type:** Terraform Security Check
    
    Security group rules should include a description for auditing purposes.
    
    Simplifies auditing, debugging, and managing security groups.
    
    Security group rule does not have a description.
    Number	Content
    6	resource "aws_security_group" "web" {
    7	  ingress {
    8	    from_port   = 0
    9	    to_port     = 65535
    10	    protocol    = "tcp"
    11	    cidr_blocks = ["0.0.0.0/0"]
    12	  }
    13	}
    
    Suggested remediation:
    Add descriptions for all security groups rules
  • LowAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/Dockerfile (line 4) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. There's no rush on this one.
    See technical detail

    badapp/Dockerfile:4

    Issue class: maintainability / code quality. Location: badapp/Dockerfile:4. Rule identifier: docker:S6505.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Omitting "--ignore-scripts" can lead to the execution of shell scripts. Make sure it is safe here.
  • LowAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/infra/main.tf (line 11) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. There's no rush on this one.
    See technical detail

    badapp/infra/main.tf:11

    Issue class: maintainability / code quality. Location: badapp/infra/main.tf:11. Rule identifier: terraform:S6321.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Restrict IP addresses authorized to access administration services.
  • LowAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/infra/main.tf (line 1) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. There's no rush on this one.
    See technical detail

    badapp/infra/main.tf:1

    Issue class: maintainability / code quality. Location: badapp/infra/main.tf:1. Rule identifier: terraform:S6258.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    Omitting "logging" or acl="log-delivery-write" makes logs incomplete. Make sure it is safe here.
  • LowAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/infra/main.tf (line 1) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. There's no rush on this one.
    See technical detail

    badapp/infra/main.tf:1

    Issue class: maintainability / code quality. Location: badapp/infra/main.tf:1. Rule identifier: terraform:S6249.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    No bucket policy enforces HTTPS-only access to this bucket. Make sure it is safe here.
  • LowAutomated scan

    A part of the code is harder to work with than it needs to be

    Effort: An afternoon
    What we found
    The code in badapp/src/app.js (line 7) has grown complicated enough that changing it safely takes longer than it should.
    What it means
    Nothing is broken today, but areas like this are where bugs tend to appear later, and they slow down anyone new to the project.
    What to do
    Tidy it the next time you're working nearby — split it into smaller, clearly-named pieces rather than stopping everything to do it now. There's no rush on this one.
    See technical detail

    badapp/src/app.js:7

    Issue class: maintainability / code quality. Location: badapp/src/app.js:7. Rule identifier: javascript:S5689.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    This framework implicitly discloses version information by default. Make sure it is safe here.
  • LowAI review

    Visitor input is trusted directly in a database query

    Effort: About an hour
    What we found
    In one of your files, text that comes from whoever is using your app is dropped straight into a database instruction instead of being handled safely first.
    What it means
    A determined visitor could craft input that reads or changes data they should never be able to reach.
    What to do
    Use the database's built-in placeholders so anything a visitor types is always treated as data, never as an instruction. There's no rush on this one.
    See technical detail

    Issue class: a structural observation about how the system is designed and assembled. Rule identifier: architecture.maintainability.declared-dependencies-unused.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    package.json declares jsonwebtoken, handlebars, lodash and minimist, and the Python side declares sqlalchemy, jinja2, pyyaml and requests, yet none of the scanned source imports them — the worker uses raw sqlite3 and the routes build HTML by concatenation. Unused runtime dependencies enlarge the image and the attack surface, and the presence of jsonwebtoken suggests an auth design that was never implemented.
    
    Suggested remediation:
    Remove dependencies that nothing imports, and if JWT auth is the intended design, implement it rather than leaving the library declared.
  • LowAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/db/queries.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. There's no rush on this one.
    See technical detail

    badapp/src/db/queries.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/db/queries.js:1. Rule identifier: architecture.performance.select-star-queries.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    findUser, findOrders and findReviews all use SELECT *. Beyond leaking password_hash to the /user response, this couples the API payload to the physical schema, so adding a column silently changes what the endpoint returns and what crosses the wire.
    
    Reviewed: badapp/src/db/queries.js (lines 1-13) — quoted from the scanned source
    const { connect } = require("./client");
    
    // SQL injection: string concatenation straight into the query.
    exports.findUser = (id, cb) =>
      connect().query("SELECT * FROM users WHERE id = '" + id + "'", cb);
    
    exports.findOrders = (userId, cb) =>
      connect().query("SELECT * FROM orders WHERE user_id = '" + userId + "'", cb);
    
    // ARCHITECTURE SMELL: queries a `reviews` table that no migration creates (schema drift).
    exports.findReviews = (productId, cb) =>
      connect().query("SELECT * FROM reviews WHERE product_id = '" + productId + "'", cb);
    
    Suggested remediation:
    Enumerate the required columns in each query and map them to an explicit response shape in the route layer.
  • LowAI review

    A password or key is written directly into the code

    Effort: About an hour
    What we found
    A password or key — the sort of secret value that unlocks a service — is written straight into your code in badapp/src/routes/admin.js (line 1), so it travels with every copy of the project.
    What it means
    Anyone who ends up with a copy of the code also gets the credential: a contractor, a future teammate, or anyone who finds the code somewhere it wasn't meant to be. It can be used in your name.
    What to do
    Move the value out of the code into a protected setting your app reads at start-up, then replace the old value at the service that issued it so the exposed one stops working. There's no rush on this one.
    See technical detail

    badapp/src/routes/admin.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/routes/admin.js:1. Rule identifier: architecture.maintainability.missing-body-parser-for-post-route.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    app.js registers no express.json() or urlencoded middleware, and POST /refund reads orderId from req.query. Request bodies are therefore unavailable to any handler, and mutating operations carry their parameters in the URL where they end up in access logs and browser history.
    
    Reviewed: badapp/src/routes/admin.js (lines 1-19) — quoted from the scanned source
    const express = require("express");
    const { exec } = require("child_process");
    const paymentService = require("../services/paymentService");
    const router = express.Router();
    
    // NO AUTH ON ANY OF THESE. The architecture finding is the missing boundary, not the syntax.
    
    // shell command built from user input -> command injection
    router.get("/ping", (req, res) =>
      exec("ping -c 1 " + req.query.host, (err, stdout) => res.send(stdout)),
    );
    
    router.post("/refund", (req, res) =>
      paymentService.charge(req.query.orderId, { number: "[secret redacted]", cvv: "123", expiry: "12/30" },
        (e) => res.json({ ok: !e })),
    );
    
    module.exports = router;
    
    Suggested remediation:
    Register express.json() in app.js and change /refund to read its parameters from the request body.
  • LowAI review

    A structural point about how your app is put together

    What we found
    Looking at the shape of your project as a whole — how the pieces fit together and talk to each other — we noted something worth thinking about in badapp/src/services/orderService.js (line 1).
    What it means
    This is about design rather than a specific bug: choices like this one tend to decide how easy the app is to change, and how well it holds up as it grows.
    What to do
    Read the detail below with whoever knows the codebase best and decide together whether it's worth adjusting now or noting for later — it's a trade-off, not an emergency. There's no rush on this one.
    See technical detail

    badapp/src/services/orderService.js:1

    Issue class: a structural observation about how the system is designed and assembled. Location: badapp/src/services/orderService.js:1. Rule identifier: architecture.maintainability.unstructured-logging.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    lib/logger.js emits a timestamp and a concatenated string, and orderService interpolates the caller-supplied userId into it. There is no level, no request correlation id and no structure, so logs cannot be filtered or joined to a request, and newline characters in the input can forge log lines.
    
    Reviewed: badapp/src/services/orderService.js (lines 1-8) — quoted from the scanned source
    const queries = require("../db/queries");
    const { log } = require("../lib/logger");
    
    exports.ordersFor = (userId, cb) => {
      log("loading orders for " + userId);
      return queries.findOrders(userId, cb);
    };
    
    Suggested remediation:
    Adopt a structured logger with levels and per-request context, and pass user-supplied values as separate fields rather than concatenating them into the message.
  • InfoAutomated scan

    A dependency has a known issue with a fix available

    Effort: About an hour
    What we found
    Jinja2 (version 2.10) has a publicly known problem, and a corrected version already exists.
    What it means
    Publicly known issues are the easiest for someone to take advantage of, because the details are already published — and so is the fix.
    What to do
    Update to the version that contains the fix, then run your tests to confirm nothing else changed. This is a note for your awareness rather than a problem to fix.
    See technical detail

    badapp/worker/requirements.txt

    Issue class: a third-party dependency with a published advisory. Classified as CWE-1336. Published advisories: CVE-2025-27516. Affected dependency: jinja2 2.10. Location: badapp/worker/requirements.txt.

    Show the original technical report

    Shown exactly as our checks reported it, with two exceptions: credential values are masked, and internal system names are removed.

    Reported:
    jinja2: Jinja sandbox breakout through attr filter selecting format method
    **Target:** badapp/worker/requirements.txt
    **Type:** pip
    **Fixed version:** 3.1.6
    
    Jinja is an extensible templating engine. Prior to 3.1.6, an oversight in how the Jinja sandboxed environment interacts with the |attr filter allows an attacker that controls the content of a template to execute arbitrary Python code. To exploit the vulnerability, an attacker needs to control the content of a template. Whether that is the case depends on the type of application using Jinja. This vulnerability impacts users of applications which execute untrusted templates. Jinja's sandbox does catch calls to str.format and ensures they don't escape the sandbox. However, it's possible to use the |attr filter to get a reference to a string's plain format method, bypassing the sandbox. After the fix, the |attr filter no longer bypasses the environment's attribute lookup. This vulnerability is fixed in 3.1.6.
    
    Suggested remediation:
    3.1.6

Architecture

Back to top

A deeper look at how your project is put together — its structure, and an AI-assisted read on the design choices behind it.

Architecture identification

Small Express monolith with a separate Python worker container and SQL migrations

High confidence
  • Node.js 14 (Docker base image node:14)
  • Express 4.16.0
  • mysql (node driver) 2.16.0
  • jsonwebtoken 8.1.0
  • lodash 4.17.4, handlebars 4.0.11, minimist 1.2.0
  • Python worker: sqlite3 + subprocess (stdlib)
  • SQLAlchemy 1.2.0, requests 2.19.1, jinja2 2.10, pyyaml 3.13 (declared in worker/requirements.txt)
  • MySQL 5.7 (docker-compose db service)
  • Raw SQL migration files (db/migrations)
  • Terraform (AWS S3 bucket, security group)
  • GitHub Actions CI (ubuntu-latest)
  • nodemon 1.18.4 (dev)

Layers

HTTP routing
Express routers in src/routes split into public, users and admin; each handler parses query params and writes the response directly. app.js mounts all three, with the admin router mounted at /admin without any authentication middleware.
Service layer
src/services holds orderService (delegates to the query module) and paymentService. The layering is inconsistent: paymentService bypasses the query module and builds an INSERT against the driver connection itself.
Data access
src/db/client.js creates a new MySQL connection per call using credentials imported from a checked-in config module; src/db/queries.js builds SELECTs by string concatenation and queries a 'reviews' table that no migration defines.
Schema / migrations
db/migrations contains 0001_init.sql (users, products, orders) and 0002_add_payments.sql (payments, including card_number, card_cvv and card_expiry columns in plaintext, plus an orders.paid_at column).
Background worker
worker/db.py runs as its own compose service and reads DATABASE_URL, but connects to a local sqlite file app.db rather than the MySQL instance, and updates orders independently of the Node schema owner.
Configuration
src/config.js exports literal credential values (AWS keys, API token, admin password, JWT secret) as a module rather than reading environment variables.
Infrastructure
Dockerfile (node:14, copies the whole tree, exposes 3000), docker-compose.yml (web, db, worker), infra/main.tf (S3 backups bucket with public-read ACL, security group allowing TCP 0-65535 from 0.0.0.0/0), and a GitHub Actions push workflow.

Entry points

  • src/app.js — Process entry point; builds the Express app, mounts routers and listens on port 3000.
  • Public routes (src/routes/public.js) — GET /health returns a JSON status; GET /hello interpolates req.query.name straight into an HTML response.
  • User routes (src/routes/users.js) — GET /user and GET /orders read query params and pass them to the data layer; GET /calc passes req.query.expr to eval.
  • Admin routes (src/routes/admin.js, mounted at /admin) — GET /admin/ping passes req.query.host into child_process.exec; POST /admin/refund calls paymentService.charge. Neither has an auth check in the code shown.
  • Worker container command — docker-compose runs 'python worker/db.py' as the worker service; the file defines lookup, mark_paid, backup and run functions with no visible scheduler or queue consumer.
  • npm scripts — package.json declares 'start' and 'migrate' scripts.
  • GitHub Actions workflow — .github/workflows/ci.yml triggers on push and runs npm install && npm test, though no test files were found in the scan.

External integrations

  • MySQL 5.7 — Primary datastore for the web service, reached via the mysql Node driver and the DATABASE_URL in docker-compose.
  • SQLite (app.db) — Second, independent datastore used by the Python worker for user lookups and order status updates.
  • AWS S3 — Terraform declares a 'badapp-backups' bucket for backups; the worker also produces a local backup.tgz via tar.
  • AWS (IAM credentials) — AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY env names plus literal key values in src/config.js suggest SDK or CLI access to AWS resources.
  • Stripe — STRIPE_SECRET_KEY appears in the environment variable inventory; no client code for it is present in the scanned files, while paymentService writes card data to the local database instead.
  • GitHub — GITHUB_TOKEN env name and the Actions CI workflow using actions/checkout@v2.
  • JWT signing — jsonwebtoken dependency and JWT_SIGNING_SECRET / ADMIN_BYPASS_TOKEN env names imply token-based auth, but no verification middleware appears in the routes shown.
  • HTTP client (requests) — Declared in the worker's requirements, implying outbound HTTP calls, though no call site appears in the scanned worker code.

Architecture diagram

100%

Select a box above to see its details and connections here.

View the full diagram as a list
  • badapp — Module

    23 files, 275 lines

    The single application module (23 files, 275 lines) holding the Express HTTP layer, the SQL migrations and a Python worker subdirectory; effectively the whole codebase.

    • Connects to badapp endpoints (exposes)
    • Connects to Data model (declares schema)
    • Connects to Express (declared in badapp/package.json)
    • Connects to JWT (declared in badapp/package.json)
    • Connects to mysql (declared in badapp/package.json)
    • Connects to handlebars (declared in badapp/package.json)
    • Connects to lodash (declared in badapp/package.json)
    • Connects to minimist (declared in badapp/package.json)
  • aws_s3_bucket.backups — Service

    declares 1 port

    Terraform-declared S3 bucket in badapp/infra/main.tf, apparently for backups.

  • aws_security_group.web — Service

    declares 1 port

    Terraform security group for the web tier; the scan recorded port 65535, which usually indicates a rule spanning the entire port range and is worth reviewing.

  • badapp/Dockerfile — Service

    image node:14, declares 1 port

    Container build for the Node service, based on node:14 and exposing port 3000; that base tag is well past its maintenance window.

    • Connects to badapp (builds the application module into the node:14 image)
  • build — Service

    image ubuntu-latest

    CI job in badapp/.github/workflows/ci.yml running on ubuntu-latest.

  • db — Service

    image mysql:5.7, declares 2 ports

    MySQL 5.7 database backing the application, with 3306 surfaced in the compose port list alongside the app's 3000.

    • Connects to Data model (inferred: hosts the tables created by the SQL migrations)
  • steps — Service

    image ubuntu-latest

    Second entry parsed from the CI workflow on ubuntu-latest; likely a steps block read as a job name rather than a real second job.

  • web — Service

    image mysql:5.7, declares 2 ports

    Compose service for the HTTP app; the scan attributed the compose file's mysql:5.7 image and its 3000/3306 port pair to every service in the file, so the per-service image here is unreliable.

    • Connects to db (inferred: app uses the mysql driver against the compose MySQL service)
  • worker — Service

    image mysql:5.7, declares 2 ports

    Background worker declared in compose; its Python dependencies (SQLAlchemy 1.2.0, requests, pyyaml, jinja2) come from badapp/worker/requirements.txt.

    • Connects to SQLAlchemy (badapp/worker/requirements.txt)
    • Connects to requests (badapp/worker/requirements.txt)
    • Connects to pyyaml (badapp/worker/requirements.txt)
    • Connects to jinja2 (badapp/worker/requirements.txt)
  • badapp endpoints — Routes

    7 routes (GET, POST)

    Seven GET/POST HTTP endpoints exposed by the Express app; no test files were found anywhere in the project to cover them.

  • Data model — Data store

    4 tables, declared via sql-migrations

    Schema declared through raw SQL migrations (no dialect recorded) with four tables: users, products, orders and payments.

  • Express — External

    web, 4.16.0

    Web framework for the HTTP layer, pinned at 4.16.0 in badapp/package.json.

  • handlebars — External

    dependency

    Templating library on the Node side, presumably rendering responses for the Express routes.

  • jinja2 — External

    dependency

    Templating library declared for the Python worker.

  • JWT — External

    auth, declared as jsonwebtoken, 8.1.0

    Token-based authentication library (jsonwebtoken 8.1.0); verify that verification options and algorithms are set explicitly wherever it is used.

  • lodash — External

    dependency

    General-purpose JavaScript utility library used by the Node app.

  • minimist — External

    dependency

    Command-line argument parser, suggesting a Node CLI or script entry point alongside the web server.

  • mysql — External

    dependency

    Node MySQL driver, the app's path to the db service; with raw SQL migrations in play, check that queries are parameterised.

  • pyyaml — External

    dependency

    YAML parser in the worker; confirm it uses safe loading if it ever reads externally supplied documents.

  • requests — External

    dependency

    HTTP client for the Python worker's outbound calls.

  • SQLAlchemy — External

    orm, ==1.2.0

    ORM used by the Python worker, pinned to ==1.2.0 in badapp/worker/requirements.txt.

How your architecture holds up

The overall picture

The shape of this system — an Express web app, a separate Python worker container, SQL migration files, Docker Compose for local wiring and Terraform for cloud resources — is a sensible skeleton for a small commerce-style service. But the implementation contradicts its own structure in ways that matter: the worker talks to a SQLite file while the web app talks to MySQL, credentials are literal values in src/config.js rather than environment variables, and the admin router is mounted with no authentication despite jsonwebtoken being a dependency. The one takeaway: this reads as an unsafe demo or test fixture, not something to put in front of real users or real card data.

What genuinely works is the separation of concerns on paper. Routes are split into public, users and admin; there is a service layer; schema changes live in versioned files (0001_init.sql, 0002_add_payments.sql); slow work is pushed to a worker process so HTTP stays responsive. At 291 lines across 24 files, all of this is still cheap to fix — that is the real asset here.

The structural strains are concentrated in three places. First, data ownership: worker/db.py updates orders in a local app.db while the Node service owns the MySQL orders table, so the two will silently diverge; DATABASE_URL is read but not used to reach MySQL. Second, the layering is not enforced — paymentService goes straight to the driver rather than through src/db/queries.js, and queries.js selects from a 'reviews' table that no migration creates. Third, trust boundaries are absent by design: query params flow into eval (GET /calc), child_process.exec (GET /admin/ping) and concatenated SQL, and the payments migration stores card_number, card_cvv and card_expiry in plaintext.

Read the full assessment

Infrastructure repeats the pattern. infra/main.tf declares a public-read backups bucket and a security group opening TCP 0-65535 to 0.0.0.0/0, and CI runs npm test while the scan found no test files — so the pipeline is green by default rather than by evidence.

What’s working

  • The service boundaries are drawn along sensible lines. Routing lives in src/routes (public, users, admin), business logic in src/services, data access in src/db, and slow work in a separate worker container declared in docker-compose.yml — the right skeleton for a small app that needs to grow.
  • Schema changes are versioned as plain SQL rather than being applied by hand. db/migrations/0001_init.sql and 0002_add_payments.sql define users, products, orders and payments, and package.json exposes a 'migrate' script, so database state is reproducible.
  • The whole environment is reproducible locally. A Dockerfile (node:14, port 3000) plus docker-compose.yml wiring web, db (mysql:5.7) and worker means a new developer gets the same stack without manual setup.
  • Infrastructure is described in code rather than clicked into a console. infra/main.tf declares the S3 backups bucket and web security group, so cloud state is reviewable in the same pull request as the code that depends on it.
  • A CI pipeline already exists and fires on push. .github/workflows/ci.yml on ubuntu-latest with actions/checkout@v2 is the hook that test and lint gates can attach to later, at near-zero cost.
  • The system is small enough to correct decisively. 24 files and 291 lines across one module means the data-ownership and configuration problems can be fixed in days rather than being carried as long-term debt.

Worth reconsidering

  • Two databases claim to own the same data. The web service writes orders to MySQL via src/db/client.js, while worker/db.py reads DATABASE_URL but connects to a local sqlite app.db and marks orders paid there. These will diverge silently, and order state becomes unanswerable — this is the single most damaging structural flaw.
  • Secrets are a code module rather than configuration. src/config.js exports literal AWS keys, an API token, an admin password and a JWT secret, so every clone of the repository is a credential leak and rotating a key requires a code change and redeploy.
  • The admin surface has no gate in front of it. app.js mounts the admin router at /admin, and /admin/ping and POST /admin/refund carry no auth check, despite jsonwebtoken 8.1.0 being a dependency and JWT_SIGNING_SECRET and ADMIN_BYPASS_TOKEN appearing in the environment inventory. Refunds are reachable by anyone who can reach the port.
  • Untrusted input reaches interpreters by design, not by accident. GET /calc passes req.query.expr to eval, GET /admin/ping passes req.query.host to child_process.exec, and GET /hello interpolates req.query.name into HTML. Each is remote code execution or script injection with no input boundary anywhere in the route layer.
  • SQL is assembled by string concatenation. src/db/queries.js builds SELECTs by joining strings rather than using the mysql driver's parameter binding, so any route that forwards a query param to the data layer is an injection path.
  • Cardholder data is stored in your own database in the clear. 0002_add_payments.sql defines card_number, card_cvv and card_expiry columns, and paymentService writes to them — even though STRIPE_SECRET_KEY is in the environment, implying a processor was meant to hold this instead. This is a compliance problem as much as a technical one.
  • The service layer is not actually a boundary. orderService delegates to the query module but paymentService builds its own INSERT against the driver connection, so there is no single place to add validation, auditing or transactions.
  • The data access layer queries a table the schema does not define. src/db/queries.js reads from 'reviews', which appears in no migration — either dead code or a runtime failure waiting on a live database.
  • Cloud resources are declared wide open. infra/main.tf sets the badapp-backups bucket to public-read and a security group allowing TCP 0-65535 from 0.0.0.0/0, so backups and every container port are internet-reachable by default.
  • CI reports success without verifying anything. The workflow runs npm install && npm test, but the scan found zero test files, so the green check is decorative and offers no protection against regressions.
  • A new MySQL connection is opened per call in src/db/client.js. With no pooling, the service will exhaust connection limits under modest concurrency, and there is no place to enforce transaction scope across a multi-step operation like a refund.
  • The runtime base is long past end of life. The Dockerfile builds on node:14 and the app pins Express 4.16.0 and mysql 2.16.0, so security patches for the platform itself are no longer arriving.
  • The worker has no trigger mechanism. worker/db.py defines lookup, mark_paid, backup and run, but docker-compose just runs the script with no scheduler or queue consumer visible, so it is unclear what causes work to happen or what happens if it fails midway.
  • The worker declares dependencies it does not appear to use. sqlalchemy, requests, jinja2 and pyyaml are pinned in worker/requirements.txt while the scanned code uses stdlib sqlite3 and subprocess — either unfinished integration or unnecessary attack surface.
  • docker-compose.yml publishes 3306 alongside 3000. Exposing the database port on the host is convenient locally but is a habit that carries badly into any shared or deployed environment.

Your project's data model — its tables, columns, and how they relate to each other — mapped directly from its own schema and code.

Database diagram

100%

Select a box above to see its details and connections here.

View the full diagram as a list
  • users — Table

    badapp/db/migrations/0001_init.sql

    • id — uuid (PK)
    • email — text
    • password_hash — text
    • is_admin — boolean
    • created_at — timestamptz
  • products — Table

    badapp/db/migrations/0001_init.sql

    • id — uuid (PK)
    • sku — varchar(64)
    • name — text
    • price_cents — integer
  • orders — Table

    badapp/db/migrations/0001_init.sql

    • id — uuid (PK)
    • user_id — uuid (FK)
    • product_id — uuid (FK)
    • quantity — integer
    • status — text
    • paid_at — timestamptz · optional
    • Connects to users (user_id → id)
    • Connects to products (product_id → id)
  • payments — Table

    badapp/db/migrations/0002_add_payments.sql

    • id — uuid (PK)
    • order_id — uuid (FK)
    • amount_cents — integer
    • currency — varchar(3)
    • card_number — text
    • card_cvv — text
    • card_expiry — text
    • Connects to orders (order_id → id)

Data model dictionary

  • 4 tables
  • 22 columns
  • 3 relationships
orders6 columns · 2 foreign keys
Columns in orders
ColumnTypeNullableKeyDefault
iduuidNoPKgen_random_uuid()
user_iduuidNoFK → users.id—
product_iduuidNoFK → products.id—
quantityintegerNo—1
statustextNo—'pending'
paid_attimestamptzYes——

Indexes: orders_user_idx

badapp/db/migrations/0001_init.sql

payments7 columns · 1 foreign key
Columns in payments
ColumnTypeNullableKeyDefault
iduuidNoPKgen_random_uuid()
order_iduuidNoFK → orders.id—
amount_centsintegerNo——
currencyvarchar(3)No—'usd'
card_numbertextNo——
card_cvvtextNo——
card_expirytextNo——

Indexes: payments_order_idx

badapp/db/migrations/0002_add_payments.sql

products4 columns
Columns in products
ColumnTypeNullableKeyDefault
iduuidNoPKgen_random_uuid()
skuvarchar(64)NoUnique—
nametextNo——
price_centsintegerNo——

Checks: "price_cents" > 0

badapp/db/migrations/0001_init.sql

users5 columns
Columns in users
ColumnTypeNullableKeyDefault
iduuidNoPKgen_random_uuid()
emailtextNoUnique—
password_hashtextNo——
is_adminbooleanNo—false
created_attimestamptzNo—now()

badapp/db/migrations/0001_init.sql

A note on these results

This report reflects what the analysis could find — it’s expert guidance to act on, not a guarantee that your code is free of every issue.

Ready to see yours?

Create an account, tell us a little about what you’ve built, and pick the scan that fits. Your code is isolated during analysis and automatically deleted afterward.

Start your analysis
Sample report — The Analyzer | Kind Robots