Back to skill

Security audit

Appian Discovertechdebt

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it handles Appian credentials and exported application ZIPs in ways that create review-worthy security risk.

Install only if you are comfortable granting the skill an Appian API key that can export applications and with exported application ZIPs being saved locally. Prefer a hardened version that validates the package download origin, ignores or sanitizes remote filenames, avoids duplicate retained ZIPs, and documents how to remove stored exports.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

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. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/index.js:112
Finding
Arbitrary File Overwrite Through an Unsanitized Content-Disposition Filename<![CDATA[ ## Vulnerability Details **File Location**: `scripts/index.js:112-136` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: Medium ### Vulnerable Code ```js 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 server-provided filename is subsequently used as a filesystem path component without sanitization: ```js function saveZip(buf, rawName, deploymentUuid) { const storagePath = path.join(os.homedir(), 'appian-exports'); const zipName = rawName ? (rawName.endsWith('.zip') ? rawName : `${rawName}.zip`) : `appian-export-${deploymentUuid}.zip`; const outPath = path.join(storagePath, zipName); fs.mkdirSync(storagePath, { recursive: true }); fs.writeFileSync(outPath, buf); process.stderr.write(`ZIP: ${outPath} (${(buf.length / 1024).toFixed(1)} KB)\n`); const cwd = process.cwd(); if (cwd !== storagePath) { const cwdExp = path.join(cwd, 'appian-exports'); fs.mkdirSync(cwdExp, { recursive: true }); fs.copyFileSync(outPath, path.join(cwdExp, zipName)); } } ``` ### Technical Analysis The ZIP filename is extracted from the remote server's `Content-Disposition` header. The regular expression allows path separators and parent-directory components such as `../`. The resulting `rawName` is assigned to `zipName` and passed to `path.join` without first reducing it to a safe basename or checking the resolved destination. A value such as `../../target.zip` can therefore escape the intended `appian-exports` directory. Both `fs.writeFileSync` and `fs.copyFileSync` overwrite existing destination files by default. This turns the path traversal into a file-overwrite vulnerability wherever the process has filesystem write permission. Appending `.zip` ...[truncated 1509 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not use a server-provided filename as a filesystem path. 2. Prefer generating a local filename from the trusted deployment UUID. 3. If the remote filename must be retained, reduce it to `path.basename(rawName)`. 4. Reject names containing `/`, `\`, `..`, null bytes, control characters, or platform-specific reserved characters. 5. Resolve the final path and verify that it remains beneath the intended export directory. 6. Use exclusive file creation where overwriting is unnecessary. 7. Apply the same validation independently to both output destinations. Example containment check: ```js const safeName = path.basename(rawName || `appian-export-${deploymentUuid}.zip`); if ( safeName !== rawName || safeName.includes('..') || !/^[A-Za-z0-9._-]+\.zip$/i.test(safeName) ) { throw new Error('Unsafe ZIP filename'); } const root = path.resolve(storagePath); const outPath = path.resolve(root, safeName); if (!outPath.startsWith(`${root}${path.sep}`)) { throw new Error('ZIP path escapes the export directory'); } fs.writeFileSync(outPath, buf, { flag: 'wx' }); ``` The strongest remediation is to ignore `Content-Disposition` entirely and always generate the output filename locally from trusted data. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill declares access to sensitive capabilities via environment variables and external network calls, but it does not constrain tool scope with explicit permissions or allowed-tools. That leaves the agent runtime free to grant broader-than-necessary access, increasing the risk of unintended data access or outbound requests if the implementation or surrounding orchestration is abused.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
---
name: appian-discovertechdebt
description: Scan an Appian application for tech debt by finding objects whose SAIL definitions reference outdated versioned functions (marked by Appian with a _v suffix such as _v1, _v2). Use periodically or before a release to surface deprecated function usage. Trigger phrases — "find Appian tech debt", "check Appian for outdated functions", "scan Appian for deprecated SAIL", "which Appian objects use old functions", "audit Appian tech debt". IMPORTANT — credentials (APPIAN_BASE_URL, APPIAN_API_KEY) are already configured in the system; do NOT ask the user for them before running.
metadata:
  clawdbot:
    emoji: "🔧"
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## IMPORTANT: credentials are pre-configured

`APPIAN_BASE_URL` and `APPIAN_API_KEY` are already injected by OpenClaw at runtime. **Never ask the user for credentials before running this skill.** Just execute it with the UUID the user provided.

## How users can ask for this
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The skill downloads the full exported Appian application ZIP and persists it to disk in the user's home directory, then duplicates it into the current working directory. That behavior exceeds the minimal requirement to scan for deprecated SAIL functions in-memory and creates unnecessary at-rest copies of potentially sensitive application source/configuration, increasing exposure to local users, backups, workspace syncing, or later accidental disclosure.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/index.js:36