Security Audit Report

The mock API behind every React tutorial has 17 security findings

https://reqres.in/api/users/1
73 C Moderate risk
17 findings
7 High 5 Medium 5 Low
Share
01

About 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.

02

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.

03

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.

04

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.

05

Detailed Findings

High Severity 7
HIGH CWE-639

Sequential numeric IDs in URL path

Path contains numeric IDs (1) — easily enumerable by attackers.

Remediation

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

bolaAuthorization
HIGH CWE-639

1 ID-parameterized endpoint(s) without auth in spec

Spec defines endpoints with object IDs but no security: /agent/v1/users/{id}

Remediation

Add security requirements to all endpoints that access specific resources.

bolaAuthorization
HIGH CWE-862

Privileged endpoint accessible: /manage

/manage returned 200 without authentication. This may expose admin functionality.

Remediation

Restrict access to admin/management endpoints. Implement RBAC with proper role checks.

bflaAuthorization
HIGH CWE-915

Internal properties exposed in response

Internal fields found: _meta

Remediation

Strip internal/system properties from API responses. Use DTOs or serialization filters.

propertyAuthorization
HIGH CWE-942

CORS allows all origins (wildcard *)

Access-Control-Allow-Origin is set to *, allowing any website to make requests.

Remediation

Restrict CORS to specific trusted origins. Avoid wildcard in production.

inputValidation
HIGH CWE-770

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).

Remediation

Implement rate limiting (token bucket, sliding window) and return X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After headers.

resourceConsumption
HIGH CWE-200

API key reference in response body

Response body contains API key references. 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
Medium Severity 5
MEDIUM

Mass assignment risk: POST /api/register

Request body schema includes sensitive field names that could enable privilege escalation.

Remediation

Use separate DTOs for input and output. Allowlist writable fields explicitly.

propertyAuthorization
MEDIUM

Mass assignment risk: POST /api/login

Request body schema includes sensitive field names that could enable privilege escalation.

Remediation

Use separate DTOs for input and output. Allowlist writable fields explicitly.

propertyAuthorization
MEDIUM

Mass assignment risk: POST /agent/v1/auth/login

Request body schema includes sensitive field names that could enable privilege escalation.

Remediation

Use separate DTOs for input and output. Allowlist writable fields explicitly.

propertyAuthorization
MEDIUM CWE-200

Token value in response body

Response body contains token values. 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
MEDIUM

Multiple external URLs in API response

Response references 6 external URLs across 2 host(s): app.reqres.in, api.reqres.in

Remediation

Validate and sanitize all external URLs. Implement allowlists for trusted third-party services.

unsafeConsumption
Low Severity 5
LOW CWE-287

Missing WWW-Authenticate header on 401

The API returns 401 but doesn't indicate the authentication scheme.

Remediation

Include WWW-Authenticate header with the expected auth scheme (Bearer, Basic, etc.).

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

Request schemas lack validation constraints

20 request body schema(s) have no maxLength, maximum, pattern, or enum constraints.

Remediation

Add validation constraints (maxLength, minimum/maximum, pattern, enum) to request schemas.

inputValidation
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
LOW CWE-200

Technology exposed via X-Powered-By: ReqRes.in - Deploy backends in 30 seconds

The X-Powered-By header reveals framework details.

Remediation

Remove the X-Powered-By header in production.

inventoryManagement
06

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.

07

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 fields

The 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.

08

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.

09

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();
});
10

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.

11

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.

Frequently Asked Questions

I use ReqRes for React tutorials. Does any of this affect me?
Probably not. The findings that matter are on the non-mock surfaces (/manage, /agent/v1/*, the mass-assignment patterns in the spec). If you are consuming /api/users, /api/login, or similar classic mock endpoints, nothing in this audit changes your workflow.
Is /manage actually an admin endpoint, or is the finding noise?
We don't know — the scan is read-only, so we did not probe the endpoint further. The response code (200) and the path name both suggest operator functionality, which is why it was flagged. A ReqRes maintainer can confirm or deny in minutes; a consumer should treat it as 'probably real' until that confirmation.
Do the tokens in ReqRes login responses leak real credentials?
No. The login endpoint is a mock — the tokens it returns are demo strings that work only against the mock itself. The scanner flags them because the string shape matches common credential patterns; the finding is a false positive for ReqRes's use case. The worth-reading subtext is that a real application copying ReqRes's response shape will get the same finding on its own scans.
Why did ReqRes get a C while HTTPBin got a B with CRITICAL findings?
The score is weighted across categories, not just findings. ReqRes had seventeen findings spread across eight categories, including spec-level issues and real-looking endpoints, which lowers the weighted score. HTTPBin had fewer findings but more clustered at CRITICAL. Both scores are defensible; they're answering slightly different questions about the services.
Should I use the ReqRes OpenAPI spec as a template for my own API?
No — at least not without reviewing the security declarations first. The published spec has spec-missing security blocks on several endpoints, inconsistent versioning, and request schemas without validation constraints. As a learning artifact it's fine; as a template it propagates the issues into whatever you build from it.
Is the X-Powered-By marketing header a security risk?
Not really. It's a creative reuse of a header that usually discloses framework names, and it gets flagged because the scanner's framework-leak heuristic matches the shape. The fix (if any) is to drop the header entirely, not to change the marketing copy.
What's the single change ReqRes could make to cut the finding count in half?
Split the hostname. Put mock endpoints on mock.reqres.in with explicit 'this is a mock, fields are fake' documentation in the OpenAPI spec, and move the real product surface to api.reqres.in with real auth, real rate limits, and real security requirements. Roughly half the findings disappear immediately because the scanner can then reason about each surface on its own terms.