Initial public ModelForge release
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# Adversarial API testing
|
||||
|
||||
Every request in this suite goes to the application under test. Nothing here probes a host service,
|
||||
scans a network, or reaches anything ModelForge does not own.
|
||||
|
||||
## What is sent
|
||||
|
||||
```text
|
||||
malformed bodies empty, truncated, non-JSON, bare scalars, NaN, Infinity, null bytes,
|
||||
200-deep nesting
|
||||
wrong types integers, nulls, arrays, objects and booleans where a string is required
|
||||
oversized input 100 KB identifiers, 5 MB bodies, 5 KB enum values
|
||||
mass assignment state, restore_eligible, verified_at, encryption_key, id
|
||||
hostile strings traversal, encoded traversal, absolute and UNC paths, file:// and
|
||||
link-local URLs, SQL, template and JNDI expressions, shell metacharacters,
|
||||
script and image XSS payloads, bidi overrides, astral-plane and emoji runs
|
||||
identifiers malformed UUIDs, SQL appended to a path segment, 500-character values
|
||||
query bounds zero, negative, out-of-range, non-numeric and injected limits
|
||||
headers CR/LF and null-byte injection into a correlation id
|
||||
credentials none, empty, wrong scheme, wrong case, oversized, null-byte, injected
|
||||
```
|
||||
|
||||
## What must happen
|
||||
|
||||
A rejected value and a stored-but-inert value are both acceptable. A 5xx is not: it means input
|
||||
reached somewhere it should not have.
|
||||
|
||||
```text
|
||||
400 / 422 malformed or wrongly typed, with a typed error envelope
|
||||
403 authenticated but out of scope
|
||||
404 well-formed identifier that does not exist
|
||||
413 / 422 oversized
|
||||
never 5xx for any input in the matrix
|
||||
```
|
||||
|
||||
Every error response carries `code`, `message` and `correlation_id`, and nothing else: no
|
||||
traceback, no SQL, no driver name, no DSN, no file path, no credential. This is asserted directly,
|
||||
including with the database stopped — a dependency failure is exactly when a framework is most
|
||||
likely to leak its internals.
|
||||
|
||||
## Scope isolation
|
||||
|
||||
The capability a credential names is matched exactly. Case changes, appended whitespace, a second
|
||||
capability in the same string and zero-width characters do not widen it. A contract version is part
|
||||
of the identity: a client scoped to `rag.embedding@1` cannot reach `rag.embedding@2`.
|
||||
|
||||
A client bound to a project may only serve that project's binding, even when its capability list
|
||||
would otherwise allow more, and a deprecated binding stops serving immediately.
|
||||
|
||||
## No enumeration oracle
|
||||
|
||||
An unknown credential, a revoked one, a near-miss differing in the last character and an empty one
|
||||
all produce the same status and the same code. A caller cannot learn whether a guess was close.
|
||||
|
||||
## Mass assignment
|
||||
|
||||
Request models are `extra="forbid"`, so a caller cannot write a field the contract never offered.
|
||||
Attempting to set `state`, `restore_eligible`, `verified_at` or an `id` is a validation error rather
|
||||
than a silently ignored field — silently ignoring is how these become exploitable later.
|
||||
|
||||
## Path handling
|
||||
|
||||
Backup identities become directory names, so the identity pattern refuses traversal outright rather
|
||||
than relying on later sanitisation. Restore destinations are parsed and identifier-validated:
|
||||
non-PostgreSQL schemes, SQL appended to a database name, an empty database and a null byte are all
|
||||
refused. The M15 allowlisted-root resolution remains the second layer.
|
||||
|
||||
## A defect this suite found
|
||||
|
||||
Python's JSON parser accepts `NaN` and `Infinity`; its serialiser rejects them. The validation
|
||||
handler echoed the rejected value straight back into the error response, so a body containing one
|
||||
made the error response itself fail to serialise — turning a 422 into a server error, on input that
|
||||
needs no credential to send. Rejected values are now rendered safely and truncated, since rejected
|
||||
input is unbounded by definition.
|
||||
Reference in New Issue
Block a user