T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/fetchConversations.js:4
- Finding
- Mailbox Filter Bypass Causes Account-Wide Conversation Retrieval<![CDATA[ ## Vulnerability Details **File Location**: `index.js:10-15`; `scripts/fetchConversations.js:4-31` **Vulnerability Type**: Improper enforcement of the configured inbox access boundary **Risk Level**: High ### Vulnerable Code `index.js:10-15`: ```javascript async function fetchAllInboxes(options = {}) { const { inboxIds } = getCredentials(); const results = await Promise.all( inboxIds.map(inboxId => fetchConversations(inboxId, options)) ); return results; } ``` `scripts/fetchConversations.js:4-31`: ```javascript async function fetchConversations({ inboxId = null, status = null, folderId = null, assignedTo = null, customerId = null, number = null, modifiedSince = null, sortField = null, sortOrder = null, tag = null, query = null, page = null}) { const token = await getToken(); const parameters = new URLSearchParams({ mailbox: inboxId, status: status, folderId: folderId, assignedTo: assignedTo, customerId: customerId, number: number, modifiedSince: modifiedSince, sortField: sortField, sortOrder: sortOrder, tag: tag, query: query, page: page }); // Filter out null parameters for (const [key, value] of parameters.entries()) { if (value === null) { parameters.delete(key); } } ``` ### Technical Analysis `fetchAllInboxes` calls `fetchConversations` with two positional arguments: ```javascript fetchConversations(inboxId, options) ``` However, `fetchConversations` accepts only one object argument and destructures its properties. The numeric or string inbox ID supplied as the firs ...[truncated 2412 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Pass a single object matching the declared function interface: ```javascript async function fetchAllInboxes(options = {}) { const { inboxIds } = getCredentials(); return Promise.all( inboxIds.map(inboxId => fetchConversations({ ...options, inboxId }) ) ); } ``` 2. Reject requests that omit the mailbox identifier rather than silently issuing an unrestricted query: ```javascript async function fetchConversations(options = {}) { const { inboxId } = options; if (inboxId === null || inboxId === undefined || inboxId === '') { throw new Error('A valid inboxId is required'); } // Continue constructing the request. } ``` 3. Enforce the configured allowlist inside `fetchConversations`, not only in `fetchAllInboxes`. This prevents direct callers from requesting arbitrary mailboxes: ```javascript const { inboxIds } = getCredentials(); if (!inboxIds.map(String).includes(String(inboxId))) { throw new Error('The requested inbox is not configured for this Skill'); } ``` 4. Normalize and validate `INBOX_IDS` and `inboxId` types before comparison. Reject malformed, empty, or unexpected values. 5. Update every documented example to use the object-based API: ```javascript await fetchConversations({ inboxId: 321755, status: 'closed', sortField: 'modifiedAt', sortOrder: 'desc', page: 1 }); ``` 6. Add tests that inspect the outgoing request and verify: - The `mailbox` parameter is always present. - The mailbox value belongs to `INBOX_IDS`. - User-supplied filters are preserved. - Missing or unauthorized inbox IDs cause a local error. - `fetchAllInboxes()` does not issue an unrestricted request. ]]>
