oauth-claims
Expired, early, wrong-issuer and wrong-audience tokens are refused
The sections below are generated from the CLI (methodology 0.3-draft, CLI 0.3.0); the same text is printed by sealmark explain oauth-claims.
- pack
oauthopt-in pack- weight
- criticalcandidate
- scope
- server as deployed (the bearer tokens the endpoint accepts, and what it tells a client that has none)
- decided by
- http
- since
- 0.3-draft (methodology 0.3-draft)
- config keys
checks.oauth-claims.enabledchecks.oauth-claims.severityoauth.audienceoauth.issuerserver.headersserver.url
Probes
controla well-formed RS256 token from the harness's issuer, unexpired, with the right iss, aud and scope: initialize and tools/list must succeed, or the check is inconclusiveexpiredexp one hour in the past (iat two hours)not-yet-validnbf one hour in the futureno-expno exp claimwrong-issueriss https://sm-other-issuer.example (signed by the same key)wrong-audienceaud "sm-wrong-audience"no-audienceno aud claimother-resourceaud https://other.example/mcp: a token the same issuer minted for another resource (RFC 8707)
Settings and thresholds
Every check also takes checks.oauth-claims.enabled and checks.oauth-claims.severity; they are described once in the configuration reference.
This check has no numeric threshold.
| Key | Default | Standard | Meaning |
|---|---|---|---|
oauth.audience | none | not locked | The audience (RFC 8707 resource) the stub's tokens carry and the protected-resource metadata must name; the value of {audience}. null uses server.url in canonical form (no fragment, no trailing slash). |
oauth.issuer | none | not locked | Which OAuth issuer the server under test trusts. stub starts the harness's own authorization server on loopback (flag --oauth): {issuer} and {jwks} name it, and the caller identities ({bearer}, {bearer:a}, {bearer:b}) become tokens it signed for {audience}, sent as Authorization: Bearer. Point the server's issuer, JWKS URL and audience at those placeholders in server.env or server.args. external declares a server that trusts its own issuer: only the checks that need no harness-signed token run (oauth-discovery). null declares nothing. |
server.headers | none | not locked | HTTP headers sent to the server (url transport). Values under secret-looking names are redacted. |
server.url | none | not locked | URL of a Streamable HTTP server. Alone it must already be running; with command, Sealmark starts it and waits for this URL. |
How the verdict is established
- Scoring. critical and scored: pass = 1 point, part = 0.5, fail = 0. A fail caps the pack's grade at D, whatever the other checks scored.
What to fix
After the signature, check the claims on every request: exp is present and in the future, nbf (when present) is not in the future, iss is exactly the issuer you configured, and aud names this server's own resource URI (RFC 8707); refuse a token minted for another resource even by the same issuer. Allow at most a small clock skew (a minute).
False positives
A server that accepts tokens without exp by design (the JWT access-token profile, RFC 9068, makes exp required) fails no-exp.
False negatives
Times are an hour off, so a server with a clock skew allowance of up to an hour passes; nothing within 60 s is probed. Only the claims listed are probed (not jti replay, not typ).