T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/index.js:107
- Finding
- Appian API Key Disclosure Through an Unvalidated Package Download URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/index.js:107-117` **Vulnerability Type**: Credential disclosure and server-side request forgery through an untrusted URL **Risk Level**: High ### Vulnerable Code ```js async function downloadZip(credentials, zipUrl) { const res = await fetch(zipUrl, { headers: { 'appian-api-key': credentials.apiKey } }); if (!res.ok) throw new Error(`Download failed [${res.status}]`); const cd = res.headers.get('content-disposition') ?? ''; const fnMatch = cd.match(/filename[^;=\n]*=(['"]?)([^\n"';]+)\1/); const rawName = fnMatch?.[2]?.trim() ?? null; const buf = Buffer.from(await res.arrayBuffer()); return { buf, rawName }; } ``` The unvalidated URL originates from the deployment status response: ```js const pollData = await pollExportStatus(credentials, triggerData.uuid); if (!pollData.packageZip) throw new Error('No packageZip URL in response'); const { buf, rawName } = await downloadZip(credentials, pollData.packageZip); ``` ### Technical Analysis The `packageZip` value returned by the remote Appian deployment API is passed directly to `fetch`. The implementation does not validate: - The URL protocol - The destination hostname - Whether the destination belongs to the configured Appian instance - Whether redirects remain on an approved origin The request unconditionally includes the sensitive `appian-api-key` header. Consequently, a malicious or compromised Appian endpoint could provide an attacker-controlled package URL and cause the skill to transmit the API key to that destination. The unrestricted URL also creates a server-side request forgery primitive. The process can be induced to send an HTTP request to any address reachable from its execution environment. The API key is additionally exposed whenever the selected destination receives the custom header. ### Attack Path 1. An attacker compromises or manipulates the Appian deployment-status response. 2. The res ...[truncated 1169 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse `packageZip` with the standard `URL` class before making the request. 2. Require HTTPS and reject all other protocols. 3. Maintain an explicit allowlist of approved download origins or hostnames. 4. Only attach `appian-api-key` when the destination origin is explicitly trusted. 5. Reject URLs containing embedded credentials or unexpected ports. 6. Disable automatic redirects or validate the destination of every redirect before following it. 7. Apply connection, response, and total download timeouts. 8. Enforce a maximum response size before buffering the complete package. Example hardening approach: ```js const trustedBase = new URL(credentials.baseUrl); const downloadUrl = new URL(zipUrl); if (downloadUrl.protocol !== 'https:') { throw new Error('Package URL must use HTTPS'); } if (downloadUrl.origin !== trustedBase.origin) { throw new Error('Package URL has an untrusted origin'); } const res = await fetch(downloadUrl, { redirect: 'manual', headers: { 'appian-api-key': credentials.apiKey }, }); ``` If legitimate package downloads use a separate host, configure a narrowly scoped allowlist rather than accepting arbitrary destinations. ]]>
