The mock API behind every React tutorial has 17 security findings
https://reqres.in/api/users/1About This API
ReqRes.in started in 2016 as a pure mock REST API — a service that returned the same fake users, the same fake responses, and the same fake 204s no matter what you sent. Its job was to exist so that a frontend developer writing their first fetch call could get a real HTTP response back with almost zero setup. That role made it the default backend of roughly every introductory React, Vue, Angular, and Svelte tutorial on the internet. If you've ever clicked through a useEffect + fetch lesson on YouTube, odds are extremely high it hit /api/users/1 on reqres.in.
The project has evolved. The homepage today advertises "Deploy backends in 30 seconds" as a paid product, and the public reqres.in instance now ships a full OpenAPI spec, a real login endpoint at /api/login, a separate /agent/v1/* surface, and an internal admin path at /manage. That evolution is fine — a mock API maintainer has every right to build a real product around it — but the security posture has evolved with it, and the two conversations ("is it safe as a tutorial target?" and "is it safe as a real service?") now diverge.
The consumer of ReqRes today falls into two camps. The first is the tutorial camp: a developer copy-pasting reqres.in/api/users from a YouTube thumbnail into their first app. For this camp, the findings in this audit are mostly non-issues — you are consuming a known-mock endpoint, you are not expected to get real data back, and the patterns you are being shown are deliberately simple for teaching. The second is the integration camp: anyone who has started to rely on the real endpoints (the login flow, the mass-assignment-shaped registration, the /agent/v1/ surface) as if they were production. For this camp, the findings are the story — ReqRes is not a safe place to hang a real integration off of, and it was never meant to be.
The rest of this case study is written for both camps and signposts which findings matter for which.
Threat Model
The realistic threats against ReqRes depend on what you think ReqRes is. We'll take both readings.
If ReqRes is a mock API for tutorials
The threat is almost entirely pedagogical. Fake tokens in fake login responses are flagged by scanners as credential leaks (MEDIUM severity); they're not. The permissive CORS is flagged as a misconfiguration; on a mock service whose purpose is to be called cross-origin, it's correct. The bigger risk in this reading is that tutorial makers teach patterns which the mock endpoint is happy to serve (POST /register with arbitrary body, login responses that echo the user object including is_admin-shaped fields) and those patterns get copied into real code without the understanding that they were mock scaffolding.
If ReqRes is a real integration target
The threat is direct. The /manage endpoint returned 200 OK without authentication in our scan. We did not probe the endpoint further (read-only scan), so we cannot confirm whether its contents are admin-flavored functionality or a benign placeholder, but the path name and the 200 response are both suggestive of a surface that should be behind an auth check. The OpenAPI spec defines /agent/v1/users/{id} with no security requirement — anyone can enumerate users through that endpoint. POST /api/register and POST /api/login request schemas accept arbitrary fields with no maximum length, no pattern constraints, and fields named in a way that could drive mass-assignment privilege escalation in a real backend. The _meta field in response bodies leaks internal shape that a reverse-engineer can use to reason about the service.
Cross-camp risk: the response-trust pattern
The scan reported MEDIUM severity for "token value in response body" and HIGH for "API key reference in response body." Both are standard patterns of ReqRes's fake-login flow: the mock endpoint returns a string that looks like a token so the tutorial can demonstrate storing-and-reusing an auth token. It is correct for the mock's purpose and false-positive for a data-leak scanner. The more subtle risk is that a consuming tutorial then teaches the reader to paste the returned "token" into their own code — and because it's flagged as a scannable pattern, a real application that replicates the exact response shape will trigger the same finding on every security audit.
Methodology
middleBrick ran a black-box scan against https://reqres.in/api/users/1, the canonical tutorial endpoint. Twelve security checks executed across OWASP API Top 10 categories. Because ReqRes publishes an OpenAPI spec at a discoverable location, the scanner pulled and parsed it, and several of the seventeen findings are spec-derived rather than runtime-derived — mass-assignment patterns on request body schemas, missing security requirements on ID-parameterized endpoints, and request schemas without maxLength or pattern constraints.
The active probes that fired: a BOLA probe requesting /api/users/1 and /api/users/2 (schemas matched, as expected on a mock), a BFLA probe against /manage (returned 200, flagged), and the standard CORS preflight probes. The LLM adversarial probes did not fire — ReqRes is not an LLM endpoint and none of the fingerprint signals matched.
No destructive methods were issued, no authentication was attempted, and no spec-defined write operations were exercised. The scan is entirely based on what a reader of the public API surface can observe with GET/HEAD requests plus parsing the published spec.
Results Overview
ReqRes pulled a C grade with a score of 73 and seventeen findings. The distribution runs heavier in the middle — zero critical, seven high, five medium, five low — which is atypical for the spearhead APIs we've audited. PokéAPI and HTTPBin had fewer total findings but with more clustered at the extremes. ReqRes reads like a real API that has partially-but-not-fully implemented an auth model, which is roughly what it is.
The seven HIGH findings: sequential numeric IDs in the URL path, an ID-parameterized endpoint (/agent/v1/users/{id}) with no security requirement in the spec, an open /manage endpoint, internal _meta property exposure, wildcard CORS, missing rate-limit headers, and an API-key-reference leak in the response body. Three of those — /manage, the spec-missing security on /agent/v1/users/{id}, and the API key reference — are the ones a consumer should actually reason about. The others are classic structural findings that apply to any public API with a REST shape.
The five MEDIUM findings are all mass-assignment-shaped patterns: POST /api/register, POST /api/login, and POST /agent/v1/auth/login all accept bodies with sensitive-looking field names (variations on role, is_admin, privileged) without the spec marking them as read-only or restricted. On a real backend, mass assignment lets an attacker set those fields by including them in the POST body. On a mock, the fields are echoed back but don't touch real state. The scanner correctly reports the pattern; the interpretation depends on which version of ReqRes you're using.
The five LOW findings are hygiene issues: missing WWW-Authenticate on 401s, DELETE/PUT/PATCH advertised via OPTIONS, no URL versioning (there is a /v1/ on the agent surface but not on the main API path), no validation constraints on request schemas, and X-Powered-By: "ReqRes.in - Deploy backends in 30 seconds". That last one is unusual — most services emit a framework name in the header; ReqRes uses the space for marketing copy. The scanner flagged it as framework disclosure because the header's shape matches the pattern.
Detailed Findings
Sequential numeric IDs in URL path
Path contains numeric IDs (1) — easily enumerable by attackers.
Use UUIDs or non-sequential identifiers. Implement object-level authorization checks.
1 ID-parameterized endpoint(s) without auth in spec
Spec defines endpoints with object IDs but no security: /agent/v1/users/{id}
Add security requirements to all endpoints that access specific resources.
Privileged endpoint accessible: /manage
/manage returned 200 without authentication. This may expose admin functionality.
Restrict access to admin/management endpoints. Implement RBAC with proper role checks.
Internal properties exposed in response
Internal fields found: _meta
Strip internal/system properties from API responses. Use DTOs or serialization filters.
CORS allows all origins (wildcard *)
Access-Control-Allow-Origin is set to *, allowing any website to make requests.
Restrict CORS to specific trusted origins. Avoid wildcard in production.
Missing rate limiting headers
Response contains no X-RateLimit-* or Retry-After headers. Without rate limiting, the API is vulnerable to resource exhaustion attacks (DoS, brute force, abuse).
Implement rate limiting (token bucket, sliding window) and return X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After headers.
API key reference in response body
Response body contains API key references. This constitutes a PII/sensitive data leak (CWE-200).
Remove or mask sensitive data before returning to clients. Implement field-level access controls and output filtering.
Mass assignment risk: POST /api/register
Request body schema includes sensitive field names that could enable privilege escalation.
Use separate DTOs for input and output. Allowlist writable fields explicitly.
Mass assignment risk: POST /api/login
Request body schema includes sensitive field names that could enable privilege escalation.
Use separate DTOs for input and output. Allowlist writable fields explicitly.
Mass assignment risk: POST /agent/v1/auth/login
Request body schema includes sensitive field names that could enable privilege escalation.
Use separate DTOs for input and output. Allowlist writable fields explicitly.
Token value in response body
Response body contains token values. This constitutes a PII/sensitive data leak (CWE-200).
Remove or mask sensitive data before returning to clients. Implement field-level access controls and output filtering.
Multiple external URLs in API response
Response references 6 external URLs across 2 host(s): app.reqres.in, api.reqres.in
Validate and sanitize all external URLs. Implement allowlists for trusted third-party services.
Missing WWW-Authenticate header on 401
The API returns 401 but doesn't indicate the authentication scheme.
Include WWW-Authenticate header with the expected auth scheme (Bearer, Basic, etc.).
Dangerous HTTP methods allowed: DELETE, PUT, PATCH
The server advertises support for methods that can modify or delete resources.
Only expose HTTP methods that are actually needed. Disable TRACE, and restrict DELETE/PUT/PATCH.
Request schemas lack validation constraints
20 request body schema(s) have no maxLength, maximum, pattern, or enum constraints.
Add validation constraints (maxLength, minimum/maximum, pattern, enum) to request schemas.
No API versioning detected
The API URL doesn't include a version prefix (e.g., /v1/) and no version header is present.
Implement API versioning via URL path (/v1/), header (API-Version), or query parameter.
Technology exposed via X-Powered-By: ReqRes.in - Deploy backends in 30 seconds
The X-Powered-By header reveals framework details.
Remove the X-Powered-By header in production.
Attacker Perspective
The attacker approaching ReqRes has the same fork the threat model did. As a tutorial target, there is almost nothing to attack — the data is mocked, the tokens are fake, the responses are CDN-cached. As a real service, the attack surface is small but non-trivial.
Probe /manage further
The first move is to poke /manage with read-only requests and see what shape the response has. If it echoes an HTML admin panel, it's a UI handle to some operator functionality and should be behind auth. If it returns JSON with keys like pending_deploys or users_count, it's an internal admin API accidentally exposed. If it returns a 404-ish placeholder, the finding is noise. We didn't probe it further because the scan is read-only; a real attacker would.
Walk the /agent/v1/users/{id} range
The OpenAPI spec defines /agent/v1/users/{id} with no security requirement. If the endpoint actually enforces auth at runtime, the spec is wrong; if it doesn't, the endpoint enumerates. Either way, walking 1..100 returns information about the service's operational state and how its authentication boundary is defined.
Try mass assignment on /api/register
The request schema accepts arbitrary fields with no pattern constraint. An attacker tries:
POST /api/register
Content-Type: application/json
{ "email": "[email protected]", "password": "password", "role": "admin", "is_verified": true }On a real backend, if the server passes the body to an ORM's User.create(body) without a field allowlist, the attacker creates an admin user. On the mock, the extra fields are ignored. The scanner doesn't know which variant is live; it flags the pattern so a pentester can investigate.
Read response tokens and follow them
The MEDIUM "token value in response body" and HIGH "API key reference in response body" findings are worth probing to understand which they really are. The login response body is the obvious candidate, but the mock's tokens are useless against a real system. Of more interest is the _meta internal field, which may contain operational state — request IDs, tenant hints, deployment identifiers — that helps the attacker correlate future requests. This is a low-direct-yield finding but a high-reconnaissance one.
Analysis
The /manage finding is the one that lands hardest. The scanner issued GET /manage — no credentials, no Origin header — and received 200. We stop there, because middleBrick is read-only and we are not going to probe an operator surface without authorization. What a consumer should do is look at the response body (which the scan captured), decide whether the surface is supposed to be public, and either close it or document it as intentional.
The spec findings are the next most concrete. The OpenAPI document defines several paths with no security block, including /agent/v1/users/{id}. In an OpenAPI 3.0 spec, an empty security array (security: []) means the endpoint is explicitly public; the absence of any security declaration means the endpoint inherits the spec-level default, which in ReqRes's spec is also empty. So the scanner's reading that these endpoints have no authentication requirement is strictly correct per the spec. Whether that matches the runtime behavior is the question.
Mass-assignment patterns show up on the spec's request schemas:
// OpenAPI 3.0 snippet (abbreviated)
paths:
/api/register:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
email: { type: string }
password: { type: string }
# scanner also detected: role-shaped, privilege-shaped fieldsThe scanner's read is that a request body with an unrestricted shape is a pattern in which mass assignment is possible, not that mass assignment is confirmed. That distinction matters for how to triage the finding.
The CORS wildcard is present (Access-Control-Allow-Origin: *) without the credentials pair — so it's the HIGH-severity wildcard-only finding, not the more dangerous HTTPBin-class pair. That's the correct configuration for a mock API intended to be called from any origin in a browser tutorial.
The X-Powered-By value is unusual enough to quote: X-Powered-By: "ReqRes.in - Deploy backends in 30 seconds". The header is being used as a marketing surface rather than a framework disclosure. The scanner can't distinguish between intentional marketing and accidental framework leak, so it flags the pattern. The finding is cosmetic; we mention it because it demonstrates how creative header content confuses otherwise-sensible scanner heuristics.
Industry Context
ReqRes sits in an awkward space between "pure mock" (like original ReqRes 2016) and "real SaaS" (like Postman, Mockoon, or Beeceptor's paid tiers). Its findings profile reflects that: heavier than a pure mock, lighter than a real SaaS, and with several findings that wouldn't make sense on either endpoint of the spectrum.
Compared to other mock-API services, ReqRes now has the largest real attack surface. JSONPlaceholder is still pure mock and returns GRDB-style synthetic data with no backend state. DummyJSON has more features but scopes them carefully with documented fake-auth patterns. Mockoon, Beeceptor, and similar services expose per-inbox isolation and user-specific API keys. ReqRes is the only one of the common tutorial-backend targets that has begun to ship shared operator infrastructure (/manage, /agent/v1/*) alongside the mock endpoints.
For compliance: ReqRes's data is synthetic, so PCI-DSS, HIPAA, and GDPR are not in scope for the mock endpoints themselves. The paid "Deploy backends in 30 seconds" product is a different question — if customers deploy real user data there, the platform inherits the security posture of this audit. A SOC 2 audit of ReqRes's paid tier would almost certainly raise the same seventeen findings as concerns.
Where OWASP API Top 10 2023 maps: API1 (broken auth) on /manage, API2 (broken authentication) on the missing WWW-Authenticate and the spec-missing security declarations, API3 (BOLA) on the sequential IDs and /agent/v1/users/{id}, API6 (unrestricted access to sensitive flows) on the mass-assignment patterns, API8 (security misconfiguration) on the wildcard CORS and missing rate-limit headers, and API9 (improper inventory management) on the versioning inconsistency and framework header. Seven of the ten API Top 10 categories have representation in this one scan.
Remediation Guide
/manage returning 200 without authentication
Either put the endpoint behind real authentication (if it serves operator functionality) or explicitly document it as a public status page. Don't leave the ambiguity.
// Express-style, add auth middleware to the route
app.get('/manage', requireAuth, (req, res) => {
res.json({ ok: true, uptime: process.uptime() });
}); Spec-missing security declarations
Add security: [] explicitly to every public path and a real security block to every protected path. Makes the spec self-documenting and matches scanner expectations.
paths:
/api/users/{id}:
get:
summary: Get user (public mock)
security: []
/agent/v1/users/{id}:
get:
summary: Get user (requires auth)
security:
- bearerAuth: [] Mass-assignment shapes on register / login
Add explicit allowlist of permitted request fields in the request schema via additionalProperties: false and named properties only.
components:
schemas:
RegisterRequest:
type: object
additionalProperties: false
required: [email, password]
properties:
email: { type: string, format: email, maxLength: 200 }
password: { type: string, minLength: 8, maxLength: 100 } Internal _meta property exposed
Strip internal fields at the response-serialization layer rather than relying on clients to ignore them.
// Express response filter
app.use((req, res, next) => {
const originalJson = res.json.bind(res);
res.json = (body) => {
const clean = JSON.parse(JSON.stringify(body, (k, v) =>
k.startsWith('_') ? undefined : v
));
return originalJson(clean);
};
next();
}); No rate-limit headers
Emit X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After on every response, matching the documented policy.
import rateLimit from 'express-rate-limit';
app.use(rateLimit({
windowMs: 60_000,
max: 100,
standardHeaders: 'draft-7',
legacyHeaders: false
})); Token-shaped strings in response body
If these are demo tokens, annotate them in the OpenAPI spec with x-tutorial-demo: true so downstream consumers and scanners can distinguish them from real credentials.
components:
schemas:
LoginResponse:
type: object
properties:
token:
type: string
x-tutorial-demo: true
description: Fake token for tutorial purposes. Do not use as a credential. Missing WWW-Authenticate header on 401
Return the expected authentication scheme on every 401 response so clients know how to retry.
app.use((req, res, next) => {
if (!authenticated(req)) {
res.set('WWW-Authenticate', 'Bearer realm="ReqRes", charset="UTF-8"');
return res.status(401).json({ error: 'unauthorized' });
}
next();
}); Defense in Depth
If you maintain ReqRes-class mock infrastructure, the single highest-leverage defense is to separate the mock surface from anything operator-flavored. Put the mock endpoints on one subdomain (mock.reqres.in) with wildcard CORS, no auth, and documented "this is fake, use for tutorials only." Put the paid-product and operator surfaces on another subdomain (api.reqres.in) with real auth, real rate-limit headers, and real CORS allowlists. Today these surfaces coexist under one host and the scanner can't distinguish the two; a split hostname would close roughly half of the seventeen findings by making the mock status explicit.
Add security: [] explicitly on every OpenAPI path that is intentionally public, and add a real security requirement on every path that isn't. Most of the spec-derived findings go away once the spec accurately documents the runtime authorization behavior.
On the response-body-leak findings, the fix depends on intent. If the token strings in login responses are meant as tutorial-demo data, document them as such — a x-tutorial-demo: true extension on the OpenAPI operation tells scanners and consumers alike that these fields are part of the mock interface, not a real credential. If they are sessions for the paid product, rotate the format to something less pattern-matched (short opaque IDs without sk_-style prefixes) and add Secure/HttpOnly cookie semantics.
On the consumer side, the defense is to know which ReqRes you are using. A tutorial calling /api/users/1 is consuming the safe mock and can safely ignore this audit. A CI job that has started hitting /agent/v1/* for real traffic is consuming a different product and should read the audit carefully before it scales. Add a client-side token bucket for the documented rate limit; ReqRes doesn't emit headers but the documented ceiling is tight enough that CI pipelines running under concurrency-heavy CI runners will hit it without one.
Conclusion
ReqRes got a C with seventeen findings because it is a service that is no longer purely a mock and is not yet purely a product. Tutorial consumers can treat most of the findings as noise; integration consumers have to take them seriously. The divide is not about the severity the scanner assigned — it's about which of the two ReqRes's you are using.
The three findings that matter regardless of which camp you are in are the open /manage endpoint, the spec-missing security on /agent/v1/users/{id}, and the mass-assignment shape on the registration and login request bodies. Those are not mock-ness noise; they are operational issues that a real backend inherits if it uses the published ReqRes OpenAPI spec as a template. They are also the three findings that a pentester auditing a ReqRes-derived application will stop on first.
For the tutorial-maker audience: nothing in this audit should change how you teach fetch('https://reqres.in/api/users/1'). For the integration-builder audience: the endpoints you rely on have a real attack surface, and the OpenAPI spec you're validating against doesn't accurately describe their security requirements. Treat the spec as aspirational until ReqRes publishes security requirements on the endpoints you use, and cache + rate-limit on your side so you are not the CI consumer that triggers the next round of hardening.