Random User Generator Security Audit: 12 Findings, Including an Open /api/config
https://randomuser.me/api/About This API
Random User Generator (randomuser.me) is a free, no-auth-required REST API that returns convincingly-shaped fake user records. Each response is a JSON envelope containing one or more user objects with names, emails, addresses, phone numbers, photos hosted on the project's CDN, and — distinctively — fake login credentials including a fake hashed password and a salt. It exists to be the world's default fetch('/api/users') endpoint for any UI demo that needs realistic-looking user data without spinning up a backend.
The reach is enormous. It is the API behind a large fraction of React/Vue/Angular table tutorials, the data source in countless dashboard mockups, the fake user list in Storybook examples, and the placeholder backing for design-tool prototypes shown in technical interviews. The project has been live since 2014, runs as RandomAPI/Randomuser.me-Node on GitHub (open-source under MIT), and is maintained by a small but active team.
This audit was triggered by middleBrick scanning https://randomuser.me/api/ as part of our public-API case-study program. The findings divide cleanly into 'flagged but by-design' (most of them) and 'flagged and worth investigating' (one of them — the /api/config response). The point of this writeup is not to claim Random User Generator is unsafe; it isn't. The point is to walk a developer audience through how a scanner reads a public mock service and how to triage what it finds when the same scanner runs against your own API.
Threat Model
Random User Generator's threat model is shaped by what it actually does: it returns synthetic data on every request. There are no real users behind it, no real passwords, no real PII. The threats fall into two clean buckets.
Pedagogical threats (most of the findings)
The scanner reports a CRITICAL 'API accessible without authentication' and a HIGH 'sensitive fields exposed in response' (specifically the password field). On a real authentication API, both findings would be unequivocal data leaks. On Random User Generator, both are explicit features — the API's purpose is to return fake user records that include fake credentials so that frontend tutorials can demonstrate 'login forms with realistic data.' The scanner can't read documentation; it sees a response body containing the string "password":"..." and follows its rule. The pedagogical risk is that students who copy these patterns into their own real backends inherit the implication that 'returning a password field in a user response' is normal, when on a real API it is a CWE-200 data leak.
Operational threats (the /api/config finding)
When middleBrick probed for privileged endpoints, GET /api/config returned 200 OK without authentication. We did not investigate further — middleBrick is a read-only scanner — but the path name and the 200 are both suggestive of operator surface that should be behind some kind of authorization. Whether the endpoint serves a real config payload, a status placeholder, or an empty response is something a maintainer can confirm in seconds. From the outside, what we know is that /api/config exists, returns 200, and a path with that name is a conventional place to find configuration.
Cross-cutting risk: the X-Powered-By marketing surface
The response includes X-Powered-By: Express, which the scanner flags as framework disclosure. On a project this size, the disclosure is not actionable — Express is the open-source framework everyone already knows is used, and the value is there because the framework's default settings emit it. The reason we flag it is the same reason every general-purpose API audit does: a real production API should strip this header at the proxy layer to make framework-specific exploitation slightly harder for a casual attacker. For a mock, it's noise.
Methodology
middleBrick ran a black-box scan against https://randomuser.me/api/. Twelve security checks executed across OWASP API Top 10 categories: authentication, authorization (BOLA / BFLA / property), input validation, CORS, rate limiting, data exposure, encryption, and inventory management. The scan was read-only — no destructive HTTP methods were issued, no auth headers were sent.
The BFLA probe is the one that surfaced the /api/config finding. middleBrick maintains a small list of conventionally-privileged path suffixes (/admin, /manage, /config, /internal, /health) and issues an unauthenticated GET against each. Most return 404, which the scanner records as a clean negative. GET /api/config against randomuser.me returned 200, which the scanner records as 'privileged endpoint accessible without authentication' — HIGH severity — and stops there. Whether the response body actually contains operator-flavored content is a question the scan does not attempt to answer.
The dataExposure findings (password, email, generic 'sensitive fields') come from response-body content matching: middleBrick parses the JSON response and looks for keys whose names match common sensitive-field patterns. The matches it produced (password, email) are correct content matches; their severity in context depends on whether the data is real or synthetic, which the scanner cannot determine. On the methodology side, this is the trade-off every signature-based scanner makes — false positives on public-mock APIs in exchange for high recall on real APIs.
Results Overview
Random User Generator received a B grade with a score of 79. Twelve total findings, distributed: one CRITICAL, four HIGH, three MEDIUM, four LOW.
The CRITICAL — 'API accessible without authentication' — is the finding any unauthenticated public catalog API will receive. It is correct as a structural read but does not represent an actionable risk on this service.
The four HIGH findings are: (1) the /api/config endpoint that returned 200, (2) wildcard CORS, (3) missing rate-limit headers, and (4) 'sensitive fields exposed' (the password field). Of these, only #1 is operationally interesting. Findings #2 and #3 are structural to any public mock, and #4 is the API's documented behavior.
The three MEDIUM findings cluster around the same documented behavior: 'password field in response body,' 'email addresses in response body,' and 'object model includes mass-assignment-shaped properties (password).' All three are restating the same observation — the API includes login.password in its synthetic user objects — through three different category lenses. On a real API, the convergence would matter; on this one, it's the scanner triangulating the same documented feature.
The four LOW findings are hygiene: missing security headers (HSTS, X-Content-Type-Options, X-Frame-Options), DELETE/PUT/PATCH advertised via OPTIONS, no URL versioning, and the X-Powered-By header. None are actionable on a mock service.
Detailed Findings
API accessible without authentication
The endpoint returned 200 without any authentication credentials.
Implement authentication (API key, OAuth 2.0, or JWT) for all API endpoints.
Privileged endpoint accessible: /api/config
/api/config returned 200 without authentication. This may expose admin functionality.
Restrict access to admin/management endpoints. Implement RBAC with proper role checks.
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.
Sensitive fields exposed in response
Fields found: password
Remove sensitive fields from API responses or implement field-level filtering.
Exposure of sensitive object properties (password)
Fields password in the response indicate the object model includes sensitive properties that could be writable via PUT/PATCH (mass assignment).
Implement allowlists for writable fields. Never accept role, permission, or admin fields from client input. Use separate input/output DTOs.
Password field in response body
Response body contains password fields. 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.
Email addresses exposed in response body
Response body contains email addresses. 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.
Missing security headers (3/4)
Missing: HSTS — protocol downgrade attacks; X-Content-Type-Options — MIME sniffing; X-Frame-Options — clickjacking.
Add the following headers to all API responses: strict-transport-security, x-content-type-options, x-frame-options.
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.
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: Express
The X-Powered-By header reveals framework details.
Remove the X-Powered-By header in production.
Attacker Perspective
An attacker reading this audit has almost nothing to do against Random User Generator itself. The data is synthetic, the credentials are fake, no compromise of the service yields anything valuable. The interesting attack vectors all live in the apps that consume it.
Probe /api/config for real content
The single probe worth running is GET /api/config with verbose response inspection. If the body is a JSON object describing the service's runtime configuration (allowed origins, rate-limit budgets, image-CDN settings, version markers), the surface is real and should be behind authentication. If it returns an empty 200 or a stub, the finding is noise. We do not run this probe ourselves because middleBrick is read-only and we don't have authorization to investigate the operator surface of a third-party service.
Read tutorial code for propagated patterns
The more productive use of this audit is reading the top tutorials that use Random User Generator and looking at what patterns they teach. The most common: 'fetch users, render a table, store the result in component state.' The pattern that worries us: tutorials that call the API client-side, render the response (including login.password) into the DOM, and treat the password field as if it were a render-safe identifier. Students who carry that pattern into a real backend without understanding that 'a password field in a user response is a data leak' inherit the bug into their own code.
Watch for credential-shaped strings in client storage
The login.password on a Random User response is a fake bcrypt-shaped hash. Students who store the API response in localStorage end up with strings shaped like real password hashes sitting in browser storage. When they swap the mock for a real backend without revisiting the storage layer, those strings get replaced with real hashes and the same XSS-to-credential-exfiltration chain becomes live. The pattern is propagated unintentionally; the chain is real on the downstream code.
Analysis
The scan response showed HTTP/2 200 on GET https://randomuser.me/api/ with these headers (abbreviated):
access-control-allow-origin: *
content-type: application/json; charset=utf-8
x-powered-by: Express
strict-transport-security: max-age=15724800; includeSubDomainsThe body is a single-user envelope with the documented fields, including login.password as a hashed value, login.salt, and an email. The schema match between successive requests is high — every response shares the same set of top-level keys, which is what the scanner's BOLA probe used to confirm 'structural enumeration possible.' For a paginated catalog, that confirmation is correct and uninteresting; for a real authenticated-user API, it would be a strong signal.
The HSTS header is present but with max-age=15724800 (about 26 weeks). Google's HSTS preload list requires a minimum of 31536000 (one year). The scanner records this as a LOW-severity 'HSTS too short' finding — actionable in the sense that bumping the header value to a year is a one-line change at the reverse proxy. We did not see this exact finding in the output for randomuser.me — the LOW security-headers finding pointed at HSTS, X-Content-Type-Options, and X-Frame-Options being missing on a per-response basis, which is different from the 'too short' variant we sometimes see on other targets.
The /api/config finding fired on the BFLA probe. The scanner emits GET /api/config with no headers and no credentials, and records the response code. 200 OK fires the finding. We do not log the response body of the probe, deliberately — the scanner is operating against a service we don't own, and capturing the body of a potentially-sensitive operator endpoint is not in scope. A maintainer wanting to triage this finding would look at the route handler in RandomAPI/Randomuser.me-Node and check whether the route is defined, what it returns, and whether the visibility is intentional.
Industry Context
Random User Generator sits in the same niche as Mockaroo, JSONPlaceholder, FakeStoreAPI, and DummyJSON: 'free public mock API that backs frontend tutorials.' Compared to the others, Random User has the cleanest mandate (just users) and one of the longest track records (live since 2014). The scoring profile reflects this — the only finding that distinguishes it from a generic public mock is the /api/config endpoint, which is a service-specific surface that doesn't appear on JSONPlaceholder, DummyJSON, or FakeStoreAPI.
For compliance: there is no real PII in the responses, so GDPR / CCPA / HIPAA are not in scope for the mock data itself. The maintainer collects analytics on requests (the homepage discloses Google Analytics) — that is a separate question from the API surface and outside the scope of this audit.
OWASP API Top 10 2023 mapping: API1 (broken object-level authorization) does not strongly apply because the API has no objects to authorize against. API2 (broken authentication) is technically present but structurally — the API has no auth model. API3 (broken object property authorization) is the closest match for the password-field-in-response findings, although here the property exposure is the documented feature. API8 (security misconfiguration) covers the wildcard CORS, missing rate-limit headers, and X-Powered-By. API9 (improper inventory management) covers the no-versioning finding. The remaining categories are not represented in this scan.
Remediation Guide
/api/config returning 200 without authentication
Either document the endpoint as intentionally public (with the response shape documented in README) or move it behind a maintainer-only auth check. Don't leave the ambiguity.
// Express, add a maintainer key gate
const MAINTAINER_KEY = process.env.RANDOMUSER_MAINTAINER_KEY;
app.get('/api/config', (req, res) => {
if (req.header('x-maintainer-key') !== MAINTAINER_KEY) {
return res.status(404).json({ error: 'not found' });
}
res.json({ /* config */ });
}); Sensitive fields in response body (the password field is the documented feature)
Add an OpenAPI/JSON Schema annotation describing that login.password is synthetic so downstream consumers and security scanners can distinguish it from a real credential.
// JSON Schema for the user object
{
"type": "object",
"properties": {
"login": {
"type": "object",
"properties": {
"password": {
"type": "string",
"x-synthetic": true,
"description": "Synthetic password hash for tutorial use. Not a real credential."
}
}
}
}
} Missing rate-limit headers
Emit X-RateLimit-Limit and Retry-After matching the documented daily ceiling. Clients then have a protocol path to back off.
import rateLimit from 'express-rate-limit';
app.use(rateLimit({
windowMs: 24 * 60 * 60 * 1000,
max: 1000,
standardHeaders: 'draft-7',
legacyHeaders: false,
keyGenerator: (req) => req.ip
})); X-Powered-By header reveals Express
Disable the header at framework level. One line in the Express app initialization.
app.disable('x-powered-by'); HSTS max-age too short
Increase max-age to 31536000 (one year) and include subdomains. Required for HSTS preload list.
// Express helmet config
import helmet from 'helmet';
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
})); Defense in Depth
The first defense for any mock-API maintainer is a clear public statement of intent. A page at /about-security (or a section in the project's README) that says, in plain language, 'this API returns synthetic data; the password field is intentional and fake; do not rely on this service for production user storage' would close the gap between scanner output and actual risk for downstream consumers. Random User Generator's homepage already does much of this work; making the security-relevant claims explicit (in a place a scanner can find) would help.
The /api/config endpoint is the action item. If the route is intentional and serves public configuration metadata, it is fine; documenting that intent on the README closes the loop. If the route is a leftover from earlier development or returns operator-flavored content, putting it behind auth or removing the route is a small, scoped change.
On the consumer side, the defense is to not render Random User responses verbatim into the DOM. The fields that matter for a UI mockup are name, email, picture, location; the rest can stay in component state without being rendered. A simple wrapper that strips login, id, and similar fields before passing to the UI layer is twenty lines of code and it inherits forward when the consumer eventually swaps the mock for a real backend.
The CORS and rate-limit findings are also consumer-side defenses. A frontend that hits Random User in a tight CI loop should add an Accept-Encoding: gzip + a backoff library; the API does not advertise its rate limit, but the documented soft ceiling is 1000 requests per day per IP. A CI runner doing 50 fake users per integration test run will exhaust this in roughly twenty pipelines.
Conclusion
Random User Generator gets a B with twelve findings because it is a long-running, well-maintained public mock API that serves an intentional purpose. Most of the findings are structural to that purpose. One finding — the /api/config response — is worth a maintainer's attention because the scanner's read of that path is operationally suggestive even if the actual content is benign.
For tutorial consumers: nothing in this audit changes how you consume Random User in a UI demo. Use it the way you've been using it, and remember that the password field on the response is fake and should not be rendered or logged in your downstream code.
For the maintainer: the action item is the BFLA finding on /api/config. The other findings are structural and well-known on a service of this kind.