hunt-idor — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited hunt-idor (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.
Why IDOR pays big:
Highest-value asset types (by payout potential):
| Asset Type | Why It Pays |
|---|---|
| Financial documents / billing APIs | PII + financial data exposure (Shopify, Uber, PayPal) |
| Private repositories / source code | IP theft, critical data loss (GitHub) |
| User messages / DMs | Privacy violation at scale (Reddit) |
| Account management endpoints | User addition, deletion, privilege escalation (PayPal, Mozilla) |
| Business/org administration | Cross-tenant escalation, employee PII (Uber) |
| Content moderation/admin actions | Operational sabotage (Reddit mod logs) |
Programs that pay most for IDOR:
URL patterns that scream IDOR:
/api/v1/users/{id}/
/api/v*/orders/{order_id}
/invoices/download?id=
/reports/{uuid}/
/messages/{thread_id}
/admin/orgs/{org_id}/members
/migration/{migration_id}/files
/graphql (query params with IDs)
/api/business/{business_id}/
/vouchers/{voucher_id}/policyResponse header signals:
Content-Type: application/json on endpoints accepting raw IDsX-Frame-Options or CORS misconfigs paired with ID paramsAuthorization: Bearer tokens that are user-scoped but hit org-level resourcesJavaScript source patterns:
// Look for hardcoded or interpolated IDs in JS
fetch(`/api/v1/users/${userId}/profile`)
axios.get('/invoices/' + invoiceId)
graphql query { billingDocument(id: $docId) }
// Redux/state stores exposing foreign IDs
state.currentUser.organizationIdTech stack signals:
org_id, account_id, business_id paramsid=, _id=, uuid=, /v1/{noun}/{id}, query params with numeric/UUID values{ __schema { queryType { fields { name } } } }id argument, substitute another user's ID?sort=id or ?filter[user_id]=Basic IDOR test with curl (swap cookie/token):
# Get User A's resource ID while authenticated as A
curl -s -H "Cookie: session=USER_A_SESSION" \
https://target.com/api/v1/invoices/12345
# Replay with User B's session
curl -s -H "Cookie: session=USER_B_SESSION" \
https://target.com/api/v1/invoices/12345
# Success = 200 OK with User A's dataGraphQL IDOR test:
curl -s -X POST https://target.com/graphql \
-H "Authorization: Bearer USER_B_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query":"{ billingDocument(id: \"USER_A_DOC_ID\") { id amount pdfUrl } }"}'Enumerate sequential IDs with ffuf:
ffuf -u "https://target.com/api/v1/orders/FUZZ" \
-w ids.txt \
-H "Authorization: Bearer USER_B_TOKEN" \
-mc 200 \
-o idor_results.jsonGenerate sequential ID wordlist:
# Generate IDs around a known value
known_id = 48291
with open("ids.txt", "w") as f:
for i in range(known_id - 500, known_id + 500):
f.write(str(i) + "\n")Burp Intruder payload for IDOR scanning:
GET /api/messages/§12345§ HTTP/1.1
Host: target.com
Authorization: Bearer USER_B_TOKEN
# Mark §12345§ as injection point
# Use numeric sequential payload: 12000-13000
# Filter responses by length difference or status 200JavaScript scraping for leaked IDs:
# Find IDs in JS bundles
curl -s https://target.com/static/app.js | grep -Eo '"id":"[a-f0-9-]{36}"' | sort -u
# Find object references in API responses
curl -s -H "Cookie: session=USER_A" \
https://target.com/api/v1/dashboard | python3 -m json.tool | grep -i "_id"Grep patterns for source code review:
# Missing authorization checks in common frameworks
grep -r "findById\|findOne\|getById" --include="*.js" .
grep -r "params\[:id\]\|params\['id'\]" --include="*.rb" .
grep -r "request\.args\.get\('id'\)" --include="*.py" .
# Look for direct ORM queries without user scoping
grep -r "Model\.find(params" --include="*.js" .
# vs secure pattern: Model.find({ id: params.id, userId: req.user.id })IDOR via HTTP method tampering:
# Try undocumented methods
for method in GET POST PUT PATCH DELETE OPTIONS HEAD; do
echo "=== $method ==="
curl -s -X $method \
-H "Authorization: Bearer USER_B_TOKEN" \
https://target.com/api/v1/users/USER_A_ID/profile
done // VULNERABLE: fetches any record
const invoice = await Invoice.findById(req.params.id);
// SECURE: scopes to authenticated user
const invoice = await Invoice.findOne({ _id: req.params.id, userId: req.user.id });org_id in POST body; server uses it directly without verifying caller belongs to that orguser_id check present; org_id / tenant_id check absentDefense: UUIDs instead of sequential integers
Defense: Indirect/hashed object references
echo "dXNlcl8xMjM0NQ==" | base64 -d → user_12345Defense: Short-lived tokens per resource
Defense: Rate limiting on enumeration
Defense: Checking `user_id` in WHERE clause
/v1/ vs /v2/) — authorization logic is often version-specificDefense: CORS restrictions
Defense: "Opaque" references via server-side sessions
Location headers, error messages, or metadataDefense: Parameter filtering/WAF on common patterns
{"data": {"id": "VICTIM_ID"}}, HTTP parameter pollution ?id=own_id&id=victim_id, or parameter name variations user_id, userId, uid, accountBefore writing the report, answer all three:
Be specific: "Attacker with a valid account can send a GET request to /api/v1/invoices/{victim_invoice_id} and receive the victim's full billing document including name, address, and payment amount — without any relationship to that account."
Map to CIA triad: confidentiality (data exposed), integrity (data modified), or availability (data deleted). "Victim loses confidentiality of private financial records" or "Victim's content is deleted by a third party" — vague answers fail.
If you can't demo it reproducibly, do not file the report.
Scenario 1: Financial Data Exposure + Cross-Account Billing Fraud (Uber-style) An attacker discovers two related IDORs: one allows reading any organization's voucher policy configuration (exposing org IDs, employee email lists, and payment methods), and a second allows modifying voucher policies using those leaked IDs. Chained together, this enables the attacker to redirect charges to an arbitrary business account, expose employee PII across organizations, and take over invitation links — all without any elevated privileges beyond a basic user account. Impact: financial fraud + mass PII exposure across the B2B platform.
Scenario 2: Private Repository Read via IDOR on Migration Endpoint (GitHub-style) A migration feature allows users to upload files to a migration job. The migration_id parameter is not validated against the authenticated user's ownership. An attacker creates their own migration, observes the ID format, and substitutes another user's private migration ID — gaining read access to source code files from private repositories they have no access to. Impact: complete confidentiality bypass for private intellectual property.
Scenario 3: Account Takeover Chain via Message IDOR (Reddit-style) An attacker accesses another user's private message threads by substituting their thread_id in a messaging API endpoint. The response includes message content, metadata, and — critically — session or verification tokens sent via internal messages. The attacker leverages the token found in the messages to perform account recovery steps, escalating a read-only IDOR into full account takeover. Impact: complete account compromise of targeted users at scale.
req.user.id scoping in ORM query + missing middleware on legacy /v1/ route = unauthenticated cross-tenant data read via direct ID substitution → bulk PII dump without any session at all.PATCH /api/users/{victim_uid} accepts attacker's session + victim UID → set [email protected] → trigger password reset → reset link arrives at attacker → full ATO without ever knowing victim credentials.__schema introspection → enumerate every mutation accepting id: argument → substitute victim IDs across updateUser, deleteOrg, transferBilling mutations → mass IDOR fan-out from one introspection query.?id=own&id=victim), nested-JSON wrappers ({"data":{"id":"VICTIM"}}), and parameter-name variations (uid/userId/user_id/account) when the first direct substitution returns 403.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.