# Authentication Is Not Authorization: A Reproducible Multi-Tenant API Fault-Injection Study

**Aspirehive Security Research — Technical report v0.1**  
**Research executed:** November 2, 2026 (local controlled lab; see evidence metadata for run timestamp)  
**Published:** 2026-09-02
**Scope:** Own synthetic HTTP application, local loopback only  
**Byline:** Aspirehive Security Team

## Abstract

We designed a deterministic multi-tenant HTTP API and an 18-invariant authorization test suite. The suite covers cross-tenant read/write/delete boundaries, authorization context derived from trusted identities, tenant-scoped collection results, function-level role boundaries, and object-property assignment restrictions. We ran the same cases against one hardened configuration, three individually fault-injected variants, and one combined fault configuration: **90 individual HTTP-level test observations**.

The hardened variant satisfied 18/18 invariants. The variants with deliberately removed object checks, role/property checks, or trusted-context enforcement each violated at least four invariants, while the combined fault variant violated 11. This supports a narrow engineering conclusion: this specific invariant suite detects the deliberately injected authorization regressions in the included synthetic application. **It does not measure the prevalence of vulnerabilities in production APIs, demonstrate a new zero-day, or prove these fault patterns are exploitable in any third-party product.**

## 1. Research question

Can a small, testable suite of API authorization invariants distinguish a correctly scoped multi-tenant service from services with three common classes of access-control regressions, while preserving positive use cases and anonymous-authentication checks?

This experiment does not ask whether real organizations are vulnerable to these exact bugs; establishing that would require individually authorized assessments of production software and a representative sample.

## 2. Threat model and experimental boundaries

The synthetic service has two tenants (`alpha` and `beta`), multiple document owners and administrators, and five synthetic user identities. It supports document read, edit and deletion; collection listing; an administrator-only users endpoint; and an internal-notes endpoint. All records and bearer tokens are artificial strings. The HTTP server binds only to `127.0.0.1`, uses an ephemeral port, and never reaches external systems.

An authenticated caller from `alpha` is considered adversarial for the purposes of negative tests when attempting to read or alter `beta` documents, modify a peer's document, elevate access to an administrator-only function, submit protected object properties, or override a tenant attribute through untrusted HTTP inputs. The authenticated caller's original tenant identity remains authoritative in the hardened mode.

## 3. Variants and method

We reuse identical seed data and the same set of 18 test cases for all five variants. After every test request, application data is reset to an initial deep copy so that mutations cannot influence another case. Each observation records the HTTP response, expected policy, pass/fail decision, and before/after document state differences.

| Variant | Fault model |
|---|---|
| `secure` | Checks trusted tenant, caller's role/ownership, and a property allowlist. |
| `object` | Deliberately removes document-level tenant checks and document-list filtering. |
| `function` | Deliberately removes role/ownership guards and permits protected-property assignment. |
| `context` | Deliberately treats `X-Tenant-ID` or `?tenant=` supplied by the caller as trusted authorization context. |
| `combined` | Deliberately enables all three fault classes together. |

**Test oracles.** For negative controls, a request must be denied with the stated status *and* not modify stored data. For positive write controls, the approved title change must actually occur. For collection read tests, the visible object/user IDs must exactly match the requester's tenant. Status codes chosen in this lab (such as 403 vs. 404) are not mandatory for all real production APIs; implementations should adapt expected responses to their own security policy.

The test harness uses Python's `http.client` for actual HTTP requests and `http.server` as the lab target. No third-party modules or Internet access are required.

## 4. Measured results

| Variant | Invariants passed | Invariants violated | Failed case IDs |
|---|---:|---:|---|
| `secure` | **18 / 18** | **0** | — |
| `object` | 11 / 18 | 7 | C08, C09, C10, C11, C12, C13, C18 |
| `function` | 14 / 18 | 4 | C14, C15, C16, C17 |
| `context` | 14 / 18 | 4 | C11, C12, C13, C18 |
| `combined` | 7 / 18 | 11 | C08–C18 |

**Aggregate:** 90 HTTP observations; 26 violated invariants across the four deliberately insecure variants and zero in the reference configuration. The harness detected all four seeded fault configurations (4/4), but this must not be interpreted as a general detection-rate or accuracy measurement across real-world applications.

### 4.1 Cross-tenant access and object-level controls

In `object` mode, the test suite identifies cross-tenant reads and cross-tenant modifications/deletion by an administrator of a different tenant (C08–C10). Document listing also leaks foreign records in this mode (C11–C12). The test design intentionally includes a user who is an administrator *within the wrong tenant*: checking only whether a caller is an administrator does not establish which tenant's records they may modify.

### 4.2 Untrusted tenant selection

In `context` mode, an `alpha` user can redirect list behavior by adding an arbitrary `tenant` query parameter or `X-Tenant-ID` header (C11–C12). The same untrusted header can redirect access and an administrator's edit to a foreign tenant (C13, C18). In the secure variant, these caller-supplied values do not alter the tenant associated with the authenticated identity. This is a *demonstration of a seeded design mistake*, not a claim about a proxy or framework's defaults.

### 4.3 Role and object-property controls

In `function` mode, a member can reach administrator-only endpoints or internal fields and may edit another member's document (C14–C16). A permitted title-edit route also accepts protected fields (`tenant`, `owner`, `approved`) that the hardened implementation rejects (C17). This illustrates why an allowlist of mutable fields and function-level authorization must complement object-level tenant filtering.

### 4.4 Why simple authentication-only checks are insufficient

C06 and C07 reject missing and invalid credentials in **every** variant. Nonetheless, the `object`, `function` and `context` variants violate multiple authorization invariants after authenticating a valid user. This is a controlled counterexample to treating successful authentication tests as proof of adequate authorization.

## 5. Engineering recommendations

1. Derive tenant context from a verified identity, not a caller-controlled query value or a client-supplied header. If a gateway supplies tenant identity, enforce a verifiable trusted boundary and strip spoofable inbound values.
2. Enforce row/object tenant policy on *every* read, write, delete and list query, not only on entry pages or route groups.
3. Check role and, when applicable, record ownership separately from tenant scope. A role is not a universal cross-tenant authorization.
4. Explicitly allowlist fields for each mutation. Treat ownership, tenant attribution and approval state as protected business-policy fields.
5. Test negative cases with **postconditions** and collection-content checks; HTTP status alone may miss data exposure or side effects.
6. Preserve positive controls so over-restrictive policies do not masquerade as secure behavior.

## 6. Reproducibility

```bash
python --version
python run_study.py
```

The runner exits with an error if the reference hardened variant fails any invariant or if a fault-injected variant violates none. Evidence is stored in `evidence/results.json` (all 90 request observations, including state differences) and `evidence/results.csv` (compact table). The Python runtime version and UTC experiment timestamp are recorded in JSON; read the source for exact route logic and fixtures.

This experiment was run with Python **3.13.5** in an isolated Linux container. It uses only the standard library. Different supported Python versions are checked by the included GitHub Actions configuration once the repository is publicly hosted and CI is enabled.

## 7. Validity limits and open questions

- **Synthetic application:** the examined targets are deliberate test fixtures, not independent production deployments. The study says nothing about the frequency or impact of vulnerabilities outside this lab.
- **Fault selection:** all vulnerabilities were planted by the study author. Catching four planned variants does not establish discovery performance for unknown bug classes, and invariant failures overlap across variants.
- **Scale and adversarial realism:** this is a small deterministic scenario, not a broad fuzzing campaign, distributed system, database-backed SaaS product, reverse proxy, or full adversarial red-team exercise.
- **Security outcome:** zero failures in the hardened case only means it passes the 18 specified tests, not that it is free of security defects.
- **External verification:** a public release with signed/attributed source, CI, and an independently repeated run would strengthen confidence. A real-world validation against an authorized, versioned owned application would substantially strengthen the evidence.

## 8. References

1. OWASP, [API Security Top 10 — 2023](https://api-security.owasp.org/editions/2023/en/0x11-t10/).
2. OWASP, [API1:2023 Broken Object Level Authorization](https://api-security.owasp.org/editions/2023/en/0xa1-broken-object-level-authorization/).
3. OWASP, [API5:2023 Broken Function Level Authorization](https://api-security.owasp.org/editions/2023/en/0xa5-broken-function-level-authorization/).
4. OWASP WSTG, [API Broken Object Level Authorization](https://owasp.github.io/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/12-API_Testing/02-API_Broken_Object_Level_Authorization).

---

**Publication note:** This report describes an original controlled experiment prepared for Aspirehive. This release includes the technical report and recorded evidence. The original Python study source and CI configuration are not included in this release. No CVP approval or acceptance is implied.
