Security testing
Tokens and sign-in
Since 0.5.1 Requires Cloak Proxy 0.5.1 or later.
Every token in your traffic, decoded, with expiry and every host it went to, and OAuth or OIDC sign-ins as a timeline. Flags weak algorithms and missing PKCE.
Tools › Tokens and Sign-in… (⌥ ⌘ J) finds the credentials in the captured traffic and checks them:
- Tokens: bearer tokens and JWTs in
Authorizationand token headers, session cookies, tokens in query strings and request bodies, and the ones token endpoints and sign-in redirects hand out. For each: its claims decoded, a live expiry countdown, the response that issued it, and a sent to map of every host and place it went. - Sign-in flows: OAuth 2.0 and OpenID Connect hops (the authorization request, the sign-in pages, the redirect back with a code, the token request, the user-info call) grouped into one timeline per sign-in, plus refreshes, client-credentials and password grants.
- Findings for both, worst first.
It only reads what you’ve captured: nothing is sent, so it’s safe to run against anything. The inspector’s Auth tab decodes one request’s token; this window looks at all of them together, across requests.
One access token: where it came from, how long it has left, everywhere it went, and what's wrong.
Tokens
Section titled “Tokens”Select a token for its details:
| Part | What it shows |
|---|---|
| Heading | What it is (access, ID, refresh or session token), the user it’s for, JWT or Opaque, and the value masked (Show reveals it, Copy copies it whole) |
| Issued by | The response that handed it out (a token endpoint’s body, a Set-Cookie, a redirect’s URL), linked to the request. When it isn’t there, the token came from an earlier session, another device or a pinned connection |
| Expiry | Time left, ticking (orange in the last five minutes, red once expired), when it was issued and expires, and its lifetime. Opaque tokens use the expires_in from the response that issued them |
| Findings | What’s wrong with it, each with Show Requests |
| Header and claims | Every header field and claim, with the dates of exp, iat and nbf and what the standard claims mean. Decoded, not verified: Cloak Proxy doesn’t check signatures |
| Sent to | Each host and place it was sent (header Authorization, cookie session, query access_token, body refresh_token), how many requests, whether over plain HTTP, and whether the host is in scope |
Trace Value runs Trace Value on the token and adds Everywhere it went: every endpoint that carried it in any header, body or URL, including places the sent-to map doesn’t look, such as a Referer or a JSON body sent to an analytics service.
The right-click menu in the traffic list has Inspect Tokens… for a request with an Authorization header or cookies, and the Auth tab’s JWT bar shows the time left and quick warnings, with Tokens and Sign-in….
Token findings
Section titled “Token findings”| Finding | Severity | Why it matters |
|---|---|---|
Unsigned token (alg none) |
High | A server that accepts it accepts any claims anyone writes |
| No signature | High | The header names an algorithm but the signature is empty |
| Accepted after it expired | High | A request sent it after exp and got a 2xx: the server may not check expiry |
| Sent over plain HTTP | High | Anyone on the network can copy it (loopback is fine) |
The header names its own key (jku, jwk, x5u, x5c) |
Medium | A server that trusts a key from the token itself accepts forged tokens |
| Never expires | Medium | No exp: a stolen copy works until the key changes |
| Valid for more than 30 days | Medium | (More than a day is Low.) Long-lived access tokens are worth more to whoever copies them |
| Sent to a host its audience doesn’t name | Medium | aud names other hosts: a server that doesn’t check it accepts tokens meant for another service |
| Issuer over plain HTTP | Medium | Keys and metadata fetched from it can be tampered with |
| Secrets in the payload | Medium | A claim like password or secret: payloads are encoded, not encrypted |
| Sent in a URL | Medium | URLs end up in logs, history and Referer headers |
| Sent to several sites | Medium | Each of them can use it as this user |
Signed with a shared secret (HS256) |
Low | Fine if the secret is long, random and never shipped in an app |
| The key ID looks like a path or a query | Low | Servers that look kid up without checking it can be steered to another key |
| Accepted before it was valid | Low | A request before nbf got a 2xx |
| No audience | Low | No aud on an access token sent to more than one site |
| Personal data in the payload | Info | The user’s email or phone, readable by every host that receives the token |
| Also sent outside the scope | Info | With an active scope: hosts outside it received the token |
Sign-in flows
Section titled “Sign-in flows”A legacy implicit sign-in: every hop on one timeline, and what's wrong with it.
Each flow lists its client, redirect URI and scopes, its findings, the timeline (each step with its status, time, what it carried, such as state, PKCE S256, code or issued access_token, id_token, and Show to select the request), and the tokens it issued.
Cloak Proxy recognizes the authorization request by its client_id and response_type, the redirect back by its state (or the redirect URI) whether it’s a request to the app or the Location of a sign-in response, and the token request by its code. Native apps that receive the redirect on a custom scheme (com.example.shopapp://callback) still show it, from the Location header. Token requests on their own (refresh, client credentials, password, device code, token exchange) are grouped per grant, client and endpoint.
Sign-in findings
Section titled “Sign-in findings”| Finding | Severity | Why it matters |
|---|---|---|
| The state came back changed | High | The app must reject the redirect; check that it does |
| Tokens issued without the PKCE verifier | High | The code was requested with PKCE but the server didn’t ask for the verifier |
| Implicit flow | High | Tokens come back in the redirect URL. Use the code flow with PKCE |
| Tokens in the redirect’s query string | High | (In the fragment it’s Medium.) |
| An authorization code worked twice | High | Codes must be single-use |
| Sign-in over plain HTTP | High | Part of the sign-in went unencrypted |
| No state parameter | Medium | Nothing ties the redirect to this sign-in (login CSRF). Low when PKCE is used |
| The state didn’t come back | Medium | |
| No PKCE | Medium | An intercepted code can be redeemed by someone else. OAuth 2.1 requires PKCE for every client |
| PKCE with the plain method | Medium | Use S256 |
| Redirect URI over plain HTTP | Medium | |
| OpenID Connect without a nonce | Medium | For response types that return an ID token in the redirect |
| Password grant | Medium | The app handles the user’s password itself; OAuth 2.1 removes this grant |
| A refresh token worked more than once | Low | No rotation: a stolen refresh token keeps working |
| The sign-in ended with an error | Info | The error the authorization server returned |
Findings
Section titled “Findings”The Findings tab lists every finding for tokens and flows by severity. Double-click one to open its token or flow; Show Requests selects the requests in the traffic list. The copy button at the top copies them all as Markdown, with token values masked, for a report.
Every finding, worst first.
With a scope active, In scope at the top looks only at in-scope requests.
AI assistants and the command line
Section titled “AI assistants and the command line”The MCP tool inspect_tokens returns the same: a summary, the tokens, the flows or the findings (section), or one token or flow in full (id, with trace: true for its Trace Value map). Values are always masked, and token ids are fingerprints, never the token.
cloakctl tokens # counts and the worst findingscloakctl tokens --section findings # every finding as JSONcloakctl tokens tok-3f2a… --trace # one token in full, with everywhere it went