T09 · Insecure Skill Coding Practices
Warning
- Location
- src/index.ts:104
- Finding
- Agent-Controlled Identifier Enables Authenticated API Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `src/index.ts:104`, `src/index.ts:164`, and `src/index.ts:253` **Vulnerability Type**: Improper validation and encoding of URL path components **Risk Level**: Medium The same vulnerable behavior is present in the shipped executable at `dist/index.js`. ### Vulnerable Code ```ts handler: async ({ id, ...rest }) => client.patch(`/contacts/${id as string}`, rest), ``` ```ts handler: async ({ id, ...rest }) => client.patch(`/contacts/${id as string}`, rest), ``` ```ts handler: async ({ id, ...rest }) => client.patch(`/opportunities/${id as string}`, rest), ``` The resulting endpoint is concatenated directly with the API base URL: ```ts async patch(endpoint: string, body: unknown): Promise<unknown> { const res = await fetch(`${this.base}${endpoint}`, { method: 'PATCH', headers: this.headers, body: JSON.stringify(body), }); return this.parse(res); } ``` ### Technical Analysis The `id` parameters are described as UUIDs, but their tool schemas only enforce `type: 'string'`. No runtime UUID validation or path-component encoding is applied before an identifier is interpolated into a URL. Consequently, an identifier can contain URL-significant sequences such as `../`, `/`, `?`, or `#`. URL parsing performed by `fetch` can normalize dot segments and alter the intended route. For example, an identifier such as `../admin/settings` changes: ```text /api/v1/contacts/../admin/settings ``` into an effective path equivalent to: ```text /api/v1/admin/settings ``` The request retains the plugin's CRMy bearer credential. Exploitation therefore depends on whether another same-origin route accepts `PATCH` and whether the configured API key is authorized for that route, but the plugin does not constrain the request to the intended contact or opportunity resource. ### Attack Path 1. An attacker influences an agent prompt, retrieved CRM content, or another untrusted input used as a record identifier. 2. ...[truncated 882 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Enforce UUID syntax in every identifier schema, for example with an appropriate JSON Schema `format` or strict `pattern`. 2. Perform runtime validation before constructing the endpoint, because tool-schema validation should not be the only security boundary. 3. Reject identifiers containing path separators, dot segments, query delimiters, fragments, percent-encoded separators, or any characters outside the accepted UUID alphabet. 4. Encode path components as defense in depth: ```ts function requireUuid(value: unknown): string { if ( typeof value !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ) { throw new Error('Invalid record UUID'); } return value; } handler: async ({ id, ...rest }) => { const uuid = requireUuid(id); return client.patch(`/contacts/${encodeURIComponent(uuid)}`, rest); }; ``` 5. Consider changing the client API to accept validated resource names and identifiers separately rather than accepting arbitrary endpoint strings. 6. Add regression tests using values such as `../admin`, `%2e%2e%2fadmin`, `id/child`, `id?x=y`, and `id#fragment`, and verify that all are rejected before a network request is made. ]]>
