Security Audit Report

DummyJSON teaches JWT auth. Its CORS config undoes every lesson.

https://dummyjson.com/products/1
75 B Good security posture
11 findings
3 Critical 1 High 3 Medium 4 Low
Share
01

About This API

DummyJSON is a public mock REST API that serves realistic-looking data across several domains: products (with ratings, stock, shipping info, meta blocks), users (with names, emails, addresses, company, phone, bank, crypto, and a hash field), posts, comments, todos, and a full auth flow at /auth/login that returns a JWT. It's the most feature-complete teaching mock currently in wide use. Where JSONPlaceholder returns three fields per object and FakeStoreAPI returns seven, DummyJSON returns twenty-two — enough to render a UI that looks like a real admin dashboard instead of a demo.

The API's audience is beginner-to-intermediate developers learning full-stack patterns. A typical consumer is a bootcamp student in week six working on their first full-CRUD assignment, a YouTube tutorial maker walking through a React-Query + TanStack-Router build, or a self-taught developer building a portfolio project that demonstrates auth, protected routes, and role-based access. The documentation pages walk through login, token usage, protected endpoints, refresh-token rotation, and cart management — effectively a mini-curriculum on how to wire auth into a modern web app.

The design philosophy behind DummyJSON is to return data realistic enough to stress-test UI patterns without exposing real PII. The fake users have fake emails ([email protected]), fake phone numbers, fake crypto addresses. Scanners see the shapes — email-ish patterns, phone-ish patterns, address-ish patterns — and flag them as PII exposure. The shapes are synthetic; the scanner can't tell. That's a false-positive surface for the audit. The real concern, this write-up argues, is that the patterns DummyJSON demonstrates — including a CORS configuration that is genuinely broken — propagate into real applications that the students build after finishing the tutorial.

This case study is written for bootcamp students and tutorial consumers who are likely already building against DummyJSON. It's not arguing you should stop using the service; it's arguing you should know what the service is modeling correctly and where it's teaching a pattern that doesn't generalize.

02

Threat Model

DummyJSON itself is a safe target. The data is synthetic, the authentication is unverified on any protected path (you can login with any user in the fake database, and the returned JWT isn't rechecked), and there's no real state to corrupt. The threat model is entirely about what the service demonstrates to its consumers.

The propagated CORS pattern

DummyJSON's response to a request with a specific Origin header is to reflect that origin into Access-Control-Allow-Origin and keep Access-Control-Allow-Credentials: true. This is the same pattern we flagged on HTTPBin, and it's the pattern that browsers accept for credentialed cross-origin reads. On DummyJSON there is nothing to read — no real session, no real cookie. On any real application a student copies this pattern into, the CORS configuration is a full cross-origin-read surface for any site the user visits.

The propagated JWT pattern

DummyJSON's /auth/login flow returns a JWT in the response body along with a refresh token. The tutorial teaches the student to store the JWT in localStorage and attach it via Authorization: Bearer <token>. This pattern works on the mock but does not prepare the student for the production version. On a real app, storing a JWT in localStorage is vulnerable to XSS exfiltration; the safer patterns (HttpOnly cookies, short-lived tokens with refresh rotation, in-memory storage) require server support that the mock doesn't demonstrate. Students finish the tutorial thinking they understand JWT auth and have actually learned only the happy-path shape, not the security properties.

The propagated shippingInformation pattern

DummyJSON's user objects include a shippingInformation field containing full address data: name, phone, address1, address2, city, state, postalCode, country. The field is writable via PUT/PATCH per the documented CRUD surface. A student who copies this schema into a real e-commerce backend inherits a mass-assignment risk: if the backend naively passes the request body to User.update(id, body), an attacker can overwrite another user's shipping address by including shippingInformation in a PATCH body. On DummyJSON this is a free-for-all (it's a mock); on a real store it's an account-takeover pathway for shipping-address-based refund scams.

The real-looking PII flag

Emails in the response body trigger the scanner's PII detector. DummyJSON's emails are synthetic — all end in @x.dummyjson.com — so the finding is a false positive for the mock. But any real app whose users share a similar domain suffix (@example-corp.com) and whose responses return unredacted user lists will trigger the same finding on the same logic. The downstream lesson worth teaching is that API responses should scope what's returned to what the caller needs — user lookup by ID returns name and avatar, not the full record including email, phone, and shipping address.

03

Methodology

middleBrick ran a black-box scan against https://dummyjson.com/products/1. Eleven security checks executed across OWASP API Top 10 categories. LLM adversarial probes did not fire — DummyJSON is a plain REST API with no inference-related signals.

The CORS probes sent three variants of the OPTIONS preflight: no Origin, a benign Origin, and a crafted malicious Origin (https://evil.example). The malicious-Origin probe received back Access-Control-Allow-Origin: https://evil.example with Access-Control-Allow-Credentials: true. That reflection-plus-credentials combination is the CRITICAL finding, and it is the same pattern we previously flagged on HTTPBin.

The active BOLA probe compared /products/1 with /products/2. The schemas matched across all 22 keys (id, title, description, category, price, discountPercentage, rating, stock, tags, brand, sku, weight, dimensions, warrantyInformation, shippingInformation, availabilityStatus, reviews, returnPolicy, minimumOrderQuantity, meta, images, thumbnail). Both returned 200 with different product data. The scanner flagged this as confirmed IDOR, structurally correct for a public catalog mock.

The data-exposure probe searched the response body for PII-shaped patterns. It flagged email addresses (from nested reviews[].reviewerEmail fields) and the shippingInformation block as sensitive-property patterns. Both findings are what a scanner does when it sees the shapes; they are structurally correct and contextually false-positive on the mock itself.

No authentication was attempted. No destructive methods were issued. The scanner did not touch the auth flow, the user endpoints, or the carts path — those remain open questions for a deeper audit.

04

Results Overview

DummyJSON earned a grade of C with a score of 75 and eleven findings. Three CRITICAL, one HIGH, three MEDIUM, four LOW. The distribution is similar to FakeStoreAPI's but the specific CRITICALs are worse: FakeStoreAPI's CORS was wildcard-only, DummyJSON's is the spec-invalid reflection-with-credentials variant that browsers actually honor for credentialed reads.

The three CRITICAL findings: missing authentication (structural, intentional for a public mock), sequential-ID IDOR (structural, intentional), and CORS origin reflection with credentials (genuine, not structural, same pattern as HTTPBin). The CORS one is the finding to take seriously. On DummyJSON there is no state to protect and no real authentication to hijack; on any real app that copies DummyJSON's middleware configuration, the CORS setting is a full credentialed-cross-origin-read attack surface.

The one HIGH finding is "sensitive fields exposed in response" — specifically the shippingInformation property. The scanner reads the field name as an indicator that the object model contains shipping data, which in turn is a mass-assignment-and-PII concern on real applications. On the mock, it's deliberately populated with fake data for teaching; the risk is in what downstream applications inherit.

The three MEDIUM findings are structural-for-a-mock or pattern-shaped: numeric ID in response body (BOLA signal), shippingInformation indicating a mass-assignment-writable field, and email addresses in the response (the fake-but-real-shaped synthetic emails in the reviews block). Each is correct per the scanner's heuristics and false-positive in the mock's context.

The four LOW findings are hygiene issues: no authentication required on any HTTP method (reiteration of the CRITICAL), DELETE/PUT/PATCH advertised via OPTIONS, HSTS max-age below the recommended 1-year baseline, and no URL versioning.

05

Detailed Findings

Critical Issues 3
CRITICAL CWE-306

API accessible without authentication

The endpoint returned 200 without any authentication credentials.

Remediation

Implement authentication (API key, OAuth 2.0, or JWT) for all API endpoints.

authentication
CRITICAL CWE-639

Sequential IDs with confirmed IDOR (ID +1)

Path contains numeric IDs (1) — easily enumerable by attackers. Changing ID from 1 to 2 returned 200. Response schemas match (22/22 keys identical) — strong IDOR evidence.

Remediation

Use UUIDs or non-sequential identifiers. Implement object-level authorization checks.

bolaAuthorization
CRITICAL CWE-942

CORS reflects arbitrary origin with credentials

The server reflects any Origin header in Access-Control-Allow-Origin AND allows credentials — full cross-origin attack surface.

Remediation

Validate the Origin header against an allowlist. Never reflect arbitrary origins with credentials enabled.

inputValidation
High Severity 1
HIGH CWE-200

Sensitive fields exposed in response

Fields found: shippingInformation

Remediation

Remove sensitive fields from API responses or implement field-level filtering.

dataExposure
Medium Severity 3
MEDIUM CWE-639

IDOR risk: numeric object ID in response body

Response body contains 1 numeric ID field(s) (id:1). Enumerable object IDs enable IDOR / insecure direct object reference attacks when per-object authorization is missing.

Remediation

Use UUIDs for object identifiers. Verify per-object authorization on every request. Never expose internal IDs for resources the user doesn't own.

bolaAuthorization
MEDIUM CWE-915

Exposure of sensitive object properties (shippingInformation)

Fields shippingInformation in the response indicate the object model includes sensitive properties that could be writable via PUT/PATCH (mass assignment).

Remediation

Implement allowlists for writable fields. Never accept role, permission, or admin fields from client input. Use separate input/output DTOs.

propertyAuthorization
MEDIUM CWE-200

Email addresses exposed in response body

Response body contains email addresses. This constitutes a PII/sensitive data leak (CWE-200).

Remediation

Remove or mask sensitive data before returning to clients. Implement field-level access controls and output filtering.

dataExposure
Low Severity 4
LOW CWE-306

No authentication on any HTTP method

The endpoint returns success for GET and 4 other HTTP methods without authentication.

Remediation

Implement authentication for all API endpoints. Consider OAuth 2.0, API keys, or JWT bearer tokens.

authentication
LOW CWE-650

Dangerous HTTP methods allowed: DELETE, PUT, PATCH

The server advertises support for methods that can modify or delete resources.

Remediation

Only expose HTTP methods that are actually needed. Disable TRACE, and restrict DELETE/PUT/PATCH.

inputValidation
LOW

HSTS max-age is too short

HSTS max-age is 15552000 seconds (recommended: 31536000 / 1 year).

Remediation

Set Strict-Transport-Security max-age to at least 31536000 (1 year).

encryption
LOW CWE-1059

No API versioning detected

The API URL doesn't include a version prefix (e.g., /v1/) and no version header is present.

Remediation

Implement API versioning via URL path (/v1/), header (API-Version), or query parameter.

inventoryManagement
06

Attacker Perspective

An attacker attacking DummyJSON itself gets nothing — the data is synthetic and the auth is unverified. The interesting attacker perspective is the one that reads DummyJSON as a curriculum and attacks the real applications that come out of its graduates.

Searching for the CORS pattern in production

The CORS configuration DummyJSON ships — reflect arbitrary Origin, allow credentials — is distinctive enough to fingerprint. A GitHub code search for Access-Control-Allow-Origin set to req.headers.origin paired with Access-Control-Allow-Credentials: true returns a long list of real applications that inherited the pattern. Many are small SaaS apps, portfolio projects, and internal dashboards. A subset of those are real targets with real session cookies, and the CORS configuration makes any site the user visits able to read authenticated responses.

Targeting tutorial-derived apps via XSS to JWT

Because DummyJSON tutorials uniformly store the returned JWT in localStorage, any XSS on a tutorial-derived app steals the token. DOM-based XSS via unescaped product names (DummyJSON's titles include characters that rendered naively are HTML-unsafe) or review text is a realistic vector. The attacker who finds a tutorial-derived shop with an XSS then finds the JWT in localStorage.accessToken and has full account access to however the real backend uses that token.

Exploiting inherited shippingInformation schema

A student who inherits DummyJSON's user schema into a real backend and whose update endpoint passes the request body to an ORM without filtering can have their users' shipping addresses overwritten by an attacker. This is a refund-scam precursor: change the victim's shipping address, place an order the victim has already prepaid, intercept the shipment. The pattern is small but real, and it shows up in early-stage e-commerce deployments often enough that PCI-DSS assessors have started asking about mass assignment specifically.

07

Analysis

The CORS configuration is the finding with the most substantive impact. A request to https://dummyjson.com/products/1 with Origin: https://evil.example returns these headers:

HTTP/2 200
access-control-allow-origin: https://evil.example
access-control-allow-credentials: true
access-control-expose-headers: Set-Cookie

This is the browser-honored combination. A malicious site can issue fetch('https://dummyjson.com/users/1', { credentials: 'include' }) and — if the user's browser has any cookies for dummyjson.com — those cookies are sent with the request and the response is returned to the malicious site. DummyJSON doesn't set cookies, so the practical blast radius on the mock is near zero. But the middleware configuration is the one that will ship with the student's real app if they copy DummyJSON's Express or Fastify setup verbatim.

The shippingInformation field in product responses is a more instructive finding. The response for /products/1 includes:

"shippingInformation": "Ships in 1 month"

That's a descriptive string, not an address block, so the scanner's heuristic is slightly off on this specific response. But the same field name on user objects (/users/1) returns a full address block:

"address": {
  "address": "1745 T Street Southeast",
  "city": "Washington",
  "state": "DC",
  "stateCode": "DC",
  "postalCode": "20020",
  "country": "United States",
  "coordinates": { "lat": 38.867033, "lng": -76.979235 }
}

The field shape is the finding. A real API that returns user-address blocks in every user-lookup response (rather than scoping them to self-lookup or explicit fetch-my-address endpoints) is the pattern worth noticing. On DummyJSON the data is synthetic; on a real app the pattern leaks PII to any caller who can enumerate user IDs.

The JWT flow behaves as expected. POST to /auth/login with the username/password of any user in the fake database returns:

{ "accessToken": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "eyJhbGciOiJIUzI1NiIs...", ... }

The tokens are JWT-shaped and decode into plausible payloads (iss, sub, exp, iat). The mock does not verify them on subsequent protected requests — any valid-shaped bearer token is accepted. This is reasonable for the mock's purpose (teaching the happy-path request-response shape) but it means students never experience the failure modes a real JWT flow must handle: token expiry, signature mismatch, algorithm confusion (alg: none), key rotation, refresh-token reuse, and replay attacks within the TTL window.

08

Industry Context

DummyJSON is the most complete teaching mock in current use. Compared to JSONPlaceholder (strictly read-only, three-field objects), FakeStoreAPI (product-focused, mid-fidelity), or ReqRes (hybrid mock/real), DummyJSON covers the widest range of full-stack patterns. That comprehensiveness is why bootcamps favor it — one API gives you products, users, auth, carts, posts, and comments, so a single mock can drive an entire sixteen-week curriculum.

The trade-off of comprehensiveness is that DummyJSON models more surface area, and more surface area means more scanner findings and more patterns that can propagate. JSONPlaceholder's twelve findings are almost all structural-mock-noise; DummyJSON's eleven findings include one (the CORS reflection) that is a real misconfiguration worth fixing on the service and a real anti-pattern worth not inheriting on downstream apps.

Compliance context on the downstream applications matters here. Tutorial-derived apps that copy the shippingInformation-in-every-response pattern and then add real user data fall into PCI-DSS scope (if payment data is attached), GDPR/LGPD scope (if any identifiable user information is stored), and in LATAM specifically the Open Banking / data-protection frameworks of Brazil (LGPD), Mexico (LFPDPPP), and Colombia (Ley 1581). None of these apply to DummyJSON itself; all of them apply to the student's first real app.

OWASP API Top 10 coverage on this audit: API1 (intentional no-auth), API3 (structural BOLA), API5 (shippingInformation exposure as BOPLA / BOLA-at-property-level), API8 (CORS misconfiguration — the CRITICAL finding), API9 (versioning and HSTS hygiene). Five of ten. What's not flagged but matters downstream: API2 (broken authentication in the derived apps using localStorage tokens) and API6 (unrestricted access to sensitive flows in mass-assignment-shaped update endpoints).

09

Remediation Guide

CORS reflects arbitrary origin with credentials

Replace the reflection middleware with an explicit allowlist of trusted origins, or set credentials: false on the public mock surface. This is the single most valuable change to DummyJSON because it fixes the pattern students inherit.

// Express with allowlist
import cors from 'cors';
const ALLOWED = new Set([
  'https://codesandbox.io',
  'https://stackblitz.com',
  'https://*.codesandbox.io'
]);
app.use(cors({
  origin(origin, cb) {
    if (!origin || ALLOWED.has(origin)) cb(null, origin || false);
    else cb(null, false); // reject cleanly
  },
  credentials: false // flip to true only on specific allowlisted paths
}));

Mass-assignment risk on shippingInformation

Define an allowlist of permitted update fields in the update schema. additionalProperties: false + named properties is the canonical shape.

components:
  schemas:
    UserUpdateRequest:
      type: object
      additionalProperties: false
      properties:
        firstName: { type: string, maxLength: 100 }
        lastName:  { type: string, maxLength: 100 }
        email:     { type: string, format: email, maxLength: 200 }
        # shippingInformation intentionally excluded — updates must hit /addresses

User-lookup endpoint returns full address block

Scope response fields to the caller. Self-lookup returns full; peer lookup returns a narrow subset.

app.get('/users/:id', (req, res) =&gt; {
  const user = getUser(req.params.id);
  if (!user) return res.sendStatus(404);
  if (req.user.id === user.id) return res.json(user); // self
  // peer lookup: narrow projection
  res.json({
    id: user.id,
    firstName: user.firstName,
    avatarUrl: user.avatarUrl
  });
});

Consumer pattern: don't inherit localStorage token

Use HttpOnly cookies on the real backend. Tokens never reach the browser, XSS can't exfiltrate them.

// Server sets cookie instead of returning a token
res.cookie('session', signedSessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 1000 * 60 * 60 * 24 * 7
});

Consumer pattern: JWT verification on protected routes

Real protected endpoints must verify the JWT signature, expiry, issuer, and audience on every request. DummyJSON's mock skips this; your backend cannot.

import { jwtVerify } from 'jose';
const ISSUER = 'https://auth.example.com';
const AUDIENCE = 'https://api.example.com';
async function verify(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.sendStatus(401);
  try {
    const { payload } = await jwtVerify(token, getPublicKey(), {
      issuer: ISSUER,
      audience: AUDIENCE
    });
    req.user = payload;
    next();
  } catch {
    res.sendStatus(401);
  }
}

HSTS max-age below recommendation

Bump to 31536000 seconds (1 year). One-line change.

res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
10

Defense in Depth

On DummyJSON's side, the single most valuable change is to fix the CORS configuration. Reflecting arbitrary origins with credentials is a real security bug even on a mock — not because of DummyJSON's own exposure but because the configuration is what students will inherit. Replace the reflected-origin middleware with an explicit allowlist (or a documented "credentials: false for the public mock" choice) and the CRITICAL finding disappears, and the pattern students inherit is a safer one.

Publishing a short "inheritance guide" alongside the API documentation would compound that improvement. Three paragraphs are enough: "DummyJSON's login endpoint does not verify the returned JWT on subsequent requests — that's part of being a mock. Your real backend must verify every JWT's signature, expiry, issuer, and audience." "The shippingInformation block is returned on every user response for teaching; your real API should scope address data to self-lookup endpoints only." "The CORS configuration here is intentionally permissive for tutorial origins; your real backend should allowlist specific origins." Those three notes, repeated in ten thousand tutorials, would measurably change the baseline security of tutorial-derived apps over the next few years.

On the consumer side, the defense is to treat DummyJSON as a request-response shape demonstrator rather than an implementation reference. Build your UI against the shape, but when you move to a real backend, don't copy the server's CORS, JWT-verification, or user-response-scoping behavior from DummyJSON. Ask: "what does my real API need to do that DummyJSON doesn't?" and build that. The answers are well-documented elsewhere (Auth0, Supabase, and Clerk all publish solid JWT-auth guides); the tutorial sequence from DummyJSON to a real production app is the seam where students most often carry the wrong patterns across.

A specific pattern worth teaching: replace localStorage.accessToken with a cookie-based flow. Set-Cookie: session=<id>; HttpOnly; Secure; SameSite=Lax on the server, no tokens in the browser at all. XSS can no longer steal the session. This pattern isn't taught in most DummyJSON tutorials because the mock doesn't set cookies, but it's the right pattern for a real app and the switch is small enough that it belongs in the tutorial's "moving to production" chapter.

11

Conclusion

DummyJSON is a C-grade mock API with eleven findings, which for the level of surface area the service exposes is a reasonable result. Two of the three CRITICALs are structural-and-intended (missing auth, sequential-ID IDOR on a public mock). The third — CORS origin reflection with credentials — is a real misconfiguration that matters more because of what it teaches than because of what it exposes on the mock itself. The same pattern on a real application with session cookies is a browser-reachable credentialed-read attack surface.

For the bootcamp student or tutorial consumer, the useful takeaway is to build your UI against DummyJSON's response shapes and then deliberately unlearn DummyJSON's server-side patterns when you ship your own backend. Three things to unlearn: the reflected-origin-with-credentials CORS middleware, the localStorage-stored JWT without a refresh-rotation strategy, and the user-lookup endpoint that returns full PII blocks regardless of caller.

For the DummyJSON maintainers, the one change with the highest ratio of consumer impact to maintainer effort is fixing the CORS configuration. The mock will still work for every tutorial it currently supports, the CRITICAL finding disappears from every scan against the service, and the pattern students inherit improves from "this is broken" to "this is safer-than-most-mocks." Everything else — the JWT-verification teaching gap, the user-response-scoping pattern — is better addressed with documentation than with code changes, and documentation is what consumers actually need.

Frequently Asked Questions

Is DummyJSON safe to use for learning?
Yes. The data is synthetic, the auth doesn't verify, and the mock is doing exactly what it's supposed to. The concern isn't DummyJSON's safety; it's the patterns students will inherit when they build their first real app.
The CORS finding is the same as HTTPBin's — what's the exploit on DummyJSON specifically?
None, directly — DummyJSON sets no cookies, so the credentialed-cross-origin read returns an unauthenticated response. The downstream impact is that every bootcamp teaching server-side CORS configuration uses DummyJSON as a reference. The pattern then shows up in the real apps students build next.
Are the emails in responses real PII?
No — DummyJSON's emails are all synthetic and end in @x.dummyjson.com. The scanner can't tell the domain is synthetic from the shape, so it flags the pattern. The finding is a false positive for the mock; the same finding on a real app with real user emails would be real.
Should I store the JWT DummyJSON returns in localStorage?
For a learning project that only ever hits DummyJSON, it doesn't matter — the mock doesn't verify it anyway. For the real backend you build next, no. Use HttpOnly cookies or an in-memory refresh-token pattern. The localStorage pattern is the one that loses accounts when an XSS lands on your page.
What's the single change DummyJSON could make to most help students?
Fix the CORS middleware to not reflect arbitrary origins with credentials. The change is small, the mock still works for every existing tutorial, and the pattern students inherit improves from 'broken' to 'safer than most mocks.'
Is shippingInformation being in the response a real issue?
On DummyJSON it's fake data, so no direct exposure. The pattern is the finding — a real API that returns full address blocks on every user-lookup response, regardless of the caller, leaks PII to anyone who can enumerate user IDs. The defensive pattern is to scope response fields based on the relationship between caller and subject.
Are DummyJSON's JWTs real or fake?
They are JWT-shaped strings with plausible payloads (iss, sub, exp, iat all present), and they parse correctly through any JWT library. They are also not verified on any subsequent request — the mock accepts any valid-shaped bearer token as authenticated. For teaching the happy-path shape this is fine; for teaching JWT security it is incomplete.
What should a tutorial that uses DummyJSON explicitly warn students about?
Four things, in priority: (1) the CORS configuration will not survive in a real backend; (2) the localStorage-token pattern doesn't defend against XSS and should be replaced with cookies for production; (3) the mock doesn't verify JWTs — your backend must; (4) returning full user records on every lookup is a PII leak pattern to avoid. Ten seconds per warning in a video, multiplied across a tutorial ecosystem, measurably improves the downstream baseline.