secret.jwt
A JSON Web Token whose claims do not mark it as a public key appears in the text.
What it looks at#
Gitleaks’ jwt and jwt-base64 rules find the token; this check then decodes the payload
(base64url, bounded to 4 KiB) and reads the role claim. A token with "role":"anon" is
what Supabase calls the anon key: it is designed to ship in every browser bundle, so it
emits nothing. Every other token fires, including "role":"service_role" (a full-access
key that bypasses row-level security), "role":"authenticated" (a user’s session), and a
token with no role at all. A token whose payload cannot be decoded is never cleared.
An expired token (exp in the past) still fires. Severity is fixed per finding code,
the claims can still identify a person and a project, and exp is a claim the issuer
wrote, not something this gate can vouch for. A base64-wrapped JWT (the jwt-base64 rule)
is flagged without reading its claims.
Why it matters#
A JWT is a bearer credential: whoever holds an unexpired one is that user, or that service, until it expires or the signing key is rotated. A service-role or admin-scoped token is the worst case, since it usually grants unrestricted access to the whole project.
What to do#
High severity. Revoke or rotate the signing secret if the token is long-lived or
privileged, and keep service-role keys server-side only. If the token is a public anon key
and you see this finding, its payload does not say "role":"anon", so it is not one.
Example#
curl -s -X POST https://api.stormdoor.dev/v1/secrets \
-H "Authorization: Bearer sk_live_..." \
-H "content-type: application/json" \
-d '{"text":"SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIn0.Zk3pQ8rT1vWx7YbN5cLd2HgJ9mKa4sEf6uOi0tRq"}'
The response carries one secret.jwt finding. The same text with "role":"anon" in the
payload returns verdict: "clean".