· 7 min read
Most API breaches are not exotic. They come from a valid token used in a slightly wrong way, an object identifier that nobody checked ownership on, or a signature the server never really verified. Those are hard problems for scanners because none of them are visible from outside a single request. A path that returns 200 OK tells you almost nothing about whether the caller was allowed to see what came back. That gap between a reachable endpoint and an authorized action is exactly where API testing lives, and it is exactly what a generic web scanner walks straight past.
Darkmoon approaches this with specialist agents rather than one generic crawler. The sso-idp agent reasons about OAuth2, OIDC, SAML, SCIM and JWT flows across a whole identity plane, while the framework agents (nodejs-express-angular, flask, springboot, aspnet and php) carry the broken access control and object reference logic for the service behind the API. There is no single agent called "api". OpenAPI discovery is a method the orchestrator uses to feed those agents, not a product feature we are selling you.
Why API testing breaks generic scanners
A classic scanner is built around a page and a form. It crawls links, submits inputs, and grades the response for reflected patterns. An API has none of that shape. The contract lives in a spec, the state lives in tokens, and the interesting bugs are relational: this user should not be able to read that user's order, this role should not be able to reach that route, this token from service A should not be accepted by service B. You cannot see any of that by looking at one request in isolation, which is why the OWASP API Security Top 10 (2023) is dominated by authorization failures rather than injection.
Broken object level authorization sits at number one on that list, and a scanner that fires payloads at a single identity will never find it. It has no second identity to compare against, no model of ownership, and no idea which identifiers belong to whom. The failure is not a missing payload, it is a missing point of view.
Mapping the surface with httpx and katana, and OpenAPI as an oracle
Before anything is tested, the surface has to be known. The sso-idp agent uses httpx and katana to map reachable endpoints, and curl with jq to talk to the OAuth2 and OIDC discovery documents directly. When an OpenAPI or Swagger document is available, it becomes an oracle: every path, method, parameter and declared security scheme is a hypothesis the agent can test rather than guess. The spec tells the agent which routes expect a bearer token, which take a numeric object identifier, and which claim to require a given scope, and the agent's job is to check whether the server actually enforces what the contract promises.
This matters because a spec is a statement of intent, not a statement of fact. A route documented as requires: admin is only as good as the check behind it. Reading the contract lets the agent target the delta between what the API says it enforces and what it enforces in practice, which is a far denser search space than blind fuzzing. Where no spec exists, katana and directory discovery with ffuf reconstruct as much of the surface as the target will reveal.
Authorization first
Everything below is offensive tooling. Run it only against an API you own or are explicitly contracted to test, within an agreed scope and window. The sso-idp agent is scoped to a single issuer or tenant, it never follows a federation link to a third party identity provider, and it does not authenticate anywhere it was not pointed. Keep your own runs to the same discipline.
JWT abuse and OAuth2 flaws with the sso-idp agent
Tokens are where a lot of API trust quietly collapses. The sso-idp agent treats the identity provider as the trust anchor of every application behind it, and it goes after the token itself. The checks it carries map to the well known failure modes in the OWASP JWT Cheat Sheet: an alg:none signature the server still accepts, an RS256 token replayed as HS256 with the public key used as the HMAC secret (algorithm confusion), a signing secret weak enough to recover offline with hashcat, and claims like role or sub that the server trusts without rechecking.
The shape of an alg:none check is simple, and it is the kind of thing a run illustrates against a target you control:
# Illustrative only, against your own issuer.
# Take a valid token, strip the signature, set alg to none.
header='{"alg":"none","typ":"JWT"}'
payload='{"sub":"USER_001","role":"admin"}'
tok="$(printf '%s' "$header" | base64url).$(printf '%s' "$payload" | base64url)."
curl -s https://api.example.tld/v1/admin/users -H "Authorization: Bearer $tok"On the OAuth2 side, the agent reasons at the scale of the whole flow rather than one login. It looks for a leaked client_secret that lets it mint tokens as a confidential client, a permissive redirect_uri that lets an authorization code be delivered to an attacker controlled callback, and over scoped or long lived tokens that widen the blast radius of any single leak. These are the threats catalogued in RFC 6819, the OAuth 2.0 threat model, applied against a live issuer rather than read from a page. The same identity reasoning shows up in our write ups on Entra ID ROPC and MFA bypass and the privacy preserving agent design.
BOLA and object level authorization through the framework agents
Broken object level authorization is the flaw scanners miss most, and it is where the framework agents earn their place. Each of them carries an explicit broken access control model: cross user data access, cross user object modification, direct object reference without ownership validation, access to hidden admin endpoints, and both horizontal and vertical privilege escalation. That is the difference between a tool that sees a route and an agent that reasons about who is allowed to call it.
The method is comparative. Given two identities in scope, the agent requests an object as its rightful owner, records the identifier, then replays the same request carrying the second identity. If the second identity reads or modifies the first identity's object, that is BOLA, and the proof is the pair of requests with the extracted data, not a heuristic. The framework agents also watch for the mirror image, broken function level authorization, where a lower privileged token reaches an endpoint that should require an administrative role.
| OWASP API risk (2023) | What the agent looks for | Darkmoon agent |
|---|---|---|
| API1 Broken object level auth | Cross user object read and write via replayed identifiers | framework agents |
| API2 Broken authentication | alg:none, RS256 to HS256, weak signing secret, missing verification | sso-idp |
| API3 Broken object property level auth | Mass assignment and over returned fields | framework agents |
| API5 Broken function level auth | Low privilege token reaching admin routes | framework agents |
| API8 Security misconfiguration | Permissive redirect_uri, leaked client_secret, over scoped tokens | sso-idp |
Token reuse and privilege boundaries
A token is a boundary, and boundaries are made to be tested. The agents check whether a token issued for one audience is accepted by another service, whether a session token survives a role change that should have invalidated it, and whether a refresh token quietly grants more than the access token it was meant to renew. Token transfer anomalies and token predictability are called out explicitly in the framework agents, because a token that works where it should not is a privilege escalation waiting to be chained.
There is a hard constraint here that protects the target. The sso-idp agent does no password spraying and no credential stuffing against a login or token endpoint, default credential checks are capped at eleven attempts per endpoint and then it stops, and it never locks out or revokes a real account. Testing token boundaries is about acceptance and scope, not about brute forcing credentials.
Chaining low findings into a real breach path
Individually, a lot of these findings look minor. A permissive redirect_uri is a misconfiguration. A weak JWT secret is a hygiene note. A numeric object identifier is a design smell. The value of an autonomous agent is that it does not stop at the individual finding, it composes them. A leaked client_secret mints a token, a claim the server trusts turns that token into an administrative one, and a BOLA on an admin route turns administrative access into every user's data. Each hop is small, the path is critical.
This is the same discipline you see in our cloud and platform work, where a single exposed credential becomes a chain: the S3 to credential chain, the SSRF to metadata token theft, the autonomous cloud run, and the GitLab API audit. For APIs, the orchestrator dispatches the identity and framework agents through a controlled MCP layer, and the chain is assembled from what each agent proved, not from what any of them assumed.
Proof of exploitation, not a 200 OK
Every Darkmoon agent carries the same adversarial status rule, and it is what keeps API reports honest. A finding is EXPLOITED only when impact ran end to end, data extracted or access gained. It is CONFIRMED when the impact is demonstrated with the exact request, the raw response, and the extracted data. It is UNCONFIRMED, capped at low severity, when it is a real lead with impact not yet shown. Before writing the stronger label, the agent tries to break its own claim.
That rule bites hardest on APIs, because APIs generate exactly the weak signals the rule demotes. A single page application routes 200 on almost any path, so a reachable route proves nothing. A differential response, where length or status changes with input, is a lead and not a finding. A payload echoed back in JSON is not cross site scripting until it executes in a rendered sink. The agent reports all of these, and it keeps them at their real severity instead of dressing a 200 OK up as a breach.
What we do not claim
There is no dedicated "api" agent and there is no published API benchmark number in this post. OpenAPI ingestion is a discovery method here, not a measured product capability, and the transcript above is illustrative rather than a finding from a specific target. Darkmoon runs with a privacy gateway that shows the model only deterministic placeholders such as IP_PRIVATE_001 and EMAIL_001, never your real hosts, tokens or credentials, but that gateway is a policy layer and not a sandbox, and by design the placeholders still leak structure and cardinality. The honest treatment is in the privacy article.
Remediation
- Enforce object level authorization on the server for every object identifier, checking ownership against the caller's identity rather than trusting a client supplied id.
- Verify JWT signatures with a fixed allow list of algorithms, reject
alg:noneand reject HS256 where RS256 is expected, and rotate any secret weak enough to be recovered offline. - Pin OAuth2
redirect_urivalues to an exact allow list, keepclient_secretout of any client shipped code, and issue short lived, narrowly scoped tokens bound to a single audience. - Scope every token to one service, invalidate sessions on role change, and make sure a refresh token never grants more than the access it renews.
- Treat your OpenAPI spec as a contract to test against, and confirm the server actually enforces every security scheme the document declares.
FAQ
What is the difference between API pentesting and web pentesting? Web testing is organised around pages, forms and reflected input. API testing is organised around tokens, object identifiers and authorization, so the decisive bugs are relational rather than injective. A generic web scanner has one identity and no model of ownership, which is why it misses the authorization failures that dominate the OWASP API Top 10.
How does the agent test JWT security? The sso-idp agent checks for an accepted alg:none signature, RS256 to HS256 algorithm confusion, signing secrets weak enough to recover with hashcat, missing signature verification, and server trusted claims like role. Each is proven with the exact request and the server's raw response.
What is BOLA and why do scanners miss it? Broken object level authorization is a server failing to check that the caller owns the object it is asking for. Scanners miss it because they test with a single identity and cannot compare what one user is allowed to see against what another user can reach. The framework agents test it by replaying a request under a second identity and reporting the cross user access.
Can it discover endpoints from an OpenAPI spec? Yes. When a spec is available it is used as an oracle, turning every documented path, method, parameter and security scheme into a hypothesis to test, and the agent checks whether the server enforces what the contract promises. Where no spec exists, katana and ffuf reconstruct the reachable surface.
Does testing tokens risk locking real accounts? No. The sso-idp agent does no password spraying or credential stuffing, caps default credential checks at eleven attempts per endpoint, and never locks out, revokes or disables a real account. Token testing focuses on acceptance and scope, not on brute forcing credentials.
What this proves about autonomous API testing
API security is an authorization problem wearing an injection costume. The findings that matter are about who is allowed to do what, and they only surface when a tool can hold two identities, read a contract, forge a token, and then chain the small failures into one path with impact. Darkmoon does that with specialist agents rather than a generic crawler, and it labels the result by demonstrated impact, so a 200 OK stays a lead until the agent can prove it is a breach.