kunalkapadia/express-mongoose-es6-rest-api

0 errors, 13 warningsdevelop

No critical or high-risk findings. 9 worth a look.

13 warnings in the paths between 8 routes and 1 tables.

ROUTE FINDINGS7 of 8 · 12/12 rules
ROUTES
8
TABLES
1
FILES READ
12/12
RULES RUN
12/12

13 findings

9 medium4 low

  • Mediumhigh confidencePOST / makes calls the check could not follow, so its data access is unknown, not absent: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list. It does not declare authentication.

    No rule could run on POST /: it calls into code the check could not follow, so whether it reaches data — and whether that is authenticated — is unknown. A route nobody can trace is where a rescue gets hurt.

    Missing
    Evidence of what it touches. Could not follow: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list.
    Confidence
    The check is certain it could not follow these calls; it is not claiming the route is wrong.

    Intended? Trace the chain by hand or log at the data access and hit the route. If it touches no data, record it in the baseline; if it does, that is the path to review first.

    unresolved-data-reach

  • Mediumhigh confidenceGET / makes calls the check could not follow, so its data access is unknown, not absent: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list. It does not declare authentication.

    No rule could run on GET /: it calls into code the check could not follow, so whether it reaches data — and whether that is authenticated — is unknown. A route nobody can trace is where a rescue gets hurt.

    Missing
    Evidence of what it touches. Could not follow: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list.
    Confidence
    The check is certain it could not follow these calls; it is not claiming the route is wrong.

    Intended? Trace the chain by hand or log at the data access and hit the route. If it touches no data, record it in the baseline; if it does, that is the path to review first.

    unresolved-data-reach

  • Mediumhigh confidenceDELETE /:userId makes calls the check could not follow, so its data access is unknown, not absent: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list. It does not declare authentication.

    No rule could run on DELETE /:userId: it calls into code the check could not follow, so whether it reaches data — and whether that is authenticated — is unknown. A route nobody can trace is where a rescue gets hurt.

    Missing
    Evidence of what it touches. Could not follow: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list.
    Confidence
    The check is certain it could not follow these calls; it is not claiming the route is wrong.

    Intended? Trace the chain by hand or log at the data access and hit the route. If it touches no data, record it in the baseline; if it does, that is the path to review first.

    unresolved-data-reach

  • Mediumhigh confidencePUT /:userId makes calls the check could not follow, so its data access is unknown, not absent: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list. It does not declare authentication.

    No rule could run on PUT /:userId: it calls into code the check could not follow, so whether it reaches data — and whether that is authenticated — is unknown. A route nobody can trace is where a rescue gets hurt.

    Missing
    Evidence of what it touches. Could not follow: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list.
    Confidence
    The check is certain it could not follow these calls; it is not claiming the route is wrong.

    Intended? Trace the chain by hand or log at the data access and hit the route. If it touches no data, record it in the baseline; if it does, that is the path to review first.

    unresolved-data-reach

  • Mediumhigh confidenceGET /:userId makes calls the check could not follow, so its data access is unknown, not absent: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list. It does not declare authentication.

    No rule could run on GET /:userId: it calls into code the check could not follow, so whether it reaches data — and whether that is authenticated — is unknown. A route nobody can trace is where a rescue gets hurt.

    Missing
    Evidence of what it touches. Could not follow: User.get() — server/user/user.model.js exports no get; User.list() — server/user/user.model.js exports no list.
    Confidence
    The check is certain it could not follow these calls; it is not claiming the route is wrong.

    Intended? Trace the chain by hand or log at the data access and hit the route. If it touches no data, record it in the baseline; if it does, that is the path to review first.

    unresolved-data-reach

  • Mediumlow confidencePOST /login has no validator or rate limiter attached.

    POST /login accepts any body at any rate. Malformed input reaches your handler, and nothing stops a script calling it in a loop.

    Missing
    Schema validation of the request body, and a rate limit.
    Confidence
    Inline validation (zod.parse, pydantic models) and platform rate limits are not always visible to the parser. Check before acting.

    Prove it

    curl -i -X POST 'https://YOUR_API/login' \
      -H 'content-type: application/json' -d '{"unexpected": {"nested": [1,2,3]}}'
    # Expect 400. A 2xx or 500 means the body is not validated.

    Fix

    const Body = z.object({ name: z.string().max(200) }).strict();
    
    const parsed = Body.safeParse(req.body);
    if (!parsed.success) return res.status(400).json(parsed.error.flatten());

    Regression test

    test("POST /login rejects an unexpected body", async () => {
      const res = await request(app).post("/login").send({ unexpected: true });
      expect(res.status).toBe(400);
    });

    Intended? If validation and rate limiting happen upstream (an API gateway, Vercel Firewall), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidencePOST / has no validator or rate limiter attached.

    POST / accepts any body at any rate. Malformed input reaches your handler, and nothing stops a script calling it in a loop.

    Missing
    Schema validation of the request body, and a rate limit.
    Confidence
    Inline validation (zod.parse, pydantic models) and platform rate limits are not always visible to the parser. Check before acting.

    Prove it

    curl -i -X POST 'https://YOUR_API/' \
      -H 'content-type: application/json' -d '{"unexpected": {"nested": [1,2,3]}}'
    # Expect 400. A 2xx or 500 means the body is not validated.

    Fix

    const Body = z.object({ name: z.string().max(200) }).strict();
    
    const parsed = Body.safeParse(req.body);
    if (!parsed.success) return res.status(400).json(parsed.error.flatten());

    Regression test

    test("POST / rejects an unexpected body", async () => {
      const res = await request(app).post("/").send({ unexpected: true });
      expect(res.status).toBe(400);
    });

    Intended? If validation and rate limiting happen upstream (an API gateway, Vercel Firewall), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidenceDELETE /:userId has no validator or rate limiter attached.

    DELETE /:userId accepts any body at any rate. Malformed input reaches your handler, and nothing stops a script calling it in a loop.

    Missing
    Schema validation of the request body, and a rate limit.
    Confidence
    Inline validation (zod.parse, pydantic models) and platform rate limits are not always visible to the parser. Check before acting.

    Prove it

    curl -i -X DELETE 'https://YOUR_API/1' \
      -H 'content-type: application/json' -d '{"unexpected": {"nested": [1,2,3]}}'
    # Expect 400. A 2xx or 500 means the body is not validated.

    Fix

    const Body = z.object({ name: z.string().max(200) }).strict();
    
    const parsed = Body.safeParse(req.body);
    if (!parsed.success) return res.status(400).json(parsed.error.flatten());

    Regression test

    test("DELETE /:userId rejects an unexpected body", async () => {
      const res = await request(app).delete("/1").send({ unexpected: true });
      expect(res.status).toBe(400);
    });

    Intended? If validation and rate limiting happen upstream (an API gateway, Vercel Firewall), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Mediumlow confidencePUT /:userId has no validator or rate limiter attached.

    PUT /:userId accepts any body at any rate. Malformed input reaches your handler, and nothing stops a script calling it in a loop.

    Missing
    Schema validation of the request body, and a rate limit.
    Confidence
    Inline validation (zod.parse, pydantic models) and platform rate limits are not always visible to the parser. Check before acting.

    Prove it

    curl -i -X PUT 'https://YOUR_API/1' \
      -H 'content-type: application/json' -d '{"unexpected": {"nested": [1,2,3]}}'
    # Expect 400. A 2xx or 500 means the body is not validated.

    Fix

    const Body = z.object({ name: z.string().max(200) }).strict();
    
    const parsed = Body.safeParse(req.body);
    if (!parsed.success) return res.status(400).json(parsed.error.flatten());

    Regression test

    test("PUT /:userId rejects an unexpected body", async () => {
      const res = await request(app).put("/1").send({ unexpected: true });
      expect(res.status).toBe(400);
    });

    Intended? If validation and rate limiting happen upstream (an API gateway, Vercel Firewall), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Lowhigh confidenceusers is not connected to anything.

    users is connected to nothing: dead weight, or a route that was meant to use it and does not.

    Missing
    A route that uses it, or its removal.
    Confidence
    Nothing is wired to it in the design.

    Intended? If it is used by a job or another service, accept it as known debt: run wyro-check --update-baseline and commit the ledger. It stays visible and CI fails only on new findings.

    no-orphan-datastore

  • Lowlow confidenceGET /health-check has no validator or rate limiter attached.

    GET /health-check can be called at any rate. Cheap to fix, rarely urgent on a read.

    Missing
    A rate limit.
    Confidence
    Inline validation (zod.parse, pydantic models) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    // 60 requests per IP per minute.
    app.get("/health-check", rateLimit({ windowMs: 60_000, limit: 60 }), handler);

    Intended? If validation and rate limiting happen upstream (an API gateway, Vercel Firewall), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Lowlow confidenceGET / has no validator or rate limiter attached.

    GET / can be called at any rate. Cheap to fix, rarely urgent on a read.

    Missing
    A rate limit.
    Confidence
    Inline validation (zod.parse, pydantic models) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    // 60 requests per IP per minute.
    app.get("/", rateLimit({ windowMs: 60_000, limit: 60 }), handler);

    Intended? If validation and rate limiting happen upstream (an API gateway, Vercel Firewall), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

  • Lowlow confidenceGET /:userId has no validator or rate limiter attached.

    GET /:userId can be called at any rate. Cheap to fix, rarely urgent on a read.

    Missing
    A rate limit.
    Confidence
    Inline validation (zod.parse, pydantic models) and platform rate limits are not always visible to the parser. Check before acting.

    Fix

    // 60 requests per IP per minute.
    app.get("/:userId", rateLimit({ windowMs: 60_000, limit: 60 }), handler);

    Intended? If validation and rate limiting happen upstream (an API gateway, Vercel Firewall), turn the rule off for the repo: { "policy": { "rules": { "public-entry-guarded": "off" } } } in wyro.json.

    public-entry-guarded

Free account, no card. The repository opens as an editable graph.

Add this check to the README

wyro architecture badge
[![wyro architecture](https://wyro.in/api/badge/kunalkapadia/express-mongoose-es6-rest-api)](https://wyro.in/scan/kunalkapadia/express-mongoose-es6-rest-api)

It updates itself whenever the repository changes and links back to this report.

What this is

Wyro reads the repository’s routes and data models and checks the paths between them: whether a route can reach a table without passing a guard, whether a datastore holding personal data is exposed to a public read, whether an endpoint that issues credentials requires the credentials it issues.

It is rule-based, not a model. The same commit produces the same result every time, and it does not guess at business rules it cannot see. A rule with nothing to look at is reported as not having run — never as a pass.

This check runs on public source through GitHub’s own API and needs no account. A free account adds the editable architecture canvas, private repositories on paid plans, and a CI gate. No card required.

Maintain this repository? You can have this report removed, and a real vulnerability is disclosed to you privately before it is published. How public reports are handled.