Back to skill

Security audit

Appian Deploymtstatus

Security checks for vulnerabilities and agentic risk

Overview

The skill’s Appian deployment-status purpose is coherent, but its optional artifact downloads can expose the Appian API key to unvalidated URLs and write files outside the advertised export folder.

Review before installing. Use only with a tightly scoped Appian API key and a trusted Appian base URL, and avoid --download-log or --download-zip until artifact URLs are origin-validated and downloaded filenames are sanitized and contained under ~/appian-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:96
Finding

Appian API Key Disclosure Through Unvalidated Artifact URLs

Content
View full analysis

Vulnerability Details

File Location: scripts/index.js, lines 96-97 and 137-149
Vulnerability Type: Credential disclosure through server-controlled outbound requests
Risk Level: High

Vulnerable Code

js
async function downloadArtifact(url, apiKey, destDir, fallbackName) {
    const res = await fetch(url, { headers: { 'appian-api-key': apiKey } });

The unvalidated URLs are passed to this function as follows:

js
if (opts.downloadLog && data.deploymentLogUrl) {
    process.stderr.write('\nDownloading log...\n');
    const f = await downloadArtifact(data.deploymentLogUrl, credentials.apiKey, destDir, `${deploymentUuid}-log.txt`);
    if (f) downloads.push(f);
}
if (opts.downloadZip && data.packageZip) {
    process.stderr.write('\nDownloading package ZIP...\n');
    const f = await downloadArtifact(data.packageZip, credentials.apiKey, destDir, `${deploymentUuid}.zip`);
    if (f) downloads.push(f);
}

Technical Analysis

deploymentLogUrl and packageZip originate in the deployment API response. The script passes these values directly to fetch() and unconditionally attaches the sensitive appian-api-key header. It does not validate the URL scheme, hostname, port, or origin before transmitting the credential.

Consequently, a compromised, malicious, or incorrectly configured Appian endpoint can return an artifact URL under an attacker-controlled origin. When an artifact download is requested, the script sends the Appian API key to that origin. Redirect handling also requires attention because fetch() follows redirects by default; credential behavior across redirects should not be relied upon as the primary protection.

This network transmission is necessary only when the artifact endpoint is trusted and actually requires the Appian credential. Sending the credential to arbitrary API-provided origins exceeds the minimum privileges required for artifact ret ...[truncated 1325 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse every artifact URL with new URL(url) and reject malformed URLs.
  2. Require HTTPS for both the configured Appian endpoint and artifact endpoints.
  3. Maintain an explicit allowlist of trusted artifact origins. Prefer requiring the artifact URL origin to equal new URL(APPIAN_BASE_URL).origin.
  4. Attach appian-api-key only when the destination is an approved origin that requires this credential. Do not attach it to cross-origin or presigned download URLs.
  5. Disable automatic redirects with redirect: 'manual', or validate each redirect destination before issuing another credential-bearing request.
  6. Reject URLs containing unexpected credentials, ports, protocols, or hostnames.
  7. Document the permitted artifact hosts and credential-forwarding policy accurately.
  8. Apply server-side least privilege to the API key and rotate any key that may have been disclosed.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/index.js:102
Finding

Arbitrary File Overwrite Through Content-Disposition Filename Traversal

Content
View full analysis

Vulnerability Details

File Location: scripts/index.js, lines 102-106
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: High

Vulnerable Code

js
const cd        = res.headers.get('content-disposition') ?? '';
const nameMatch = cd.match(/filename[^;=\n]*=(['"]?)([^\n"';]+)\1/);
const fileName  = nameMatch?.[2]?.trim() ?? fallbackName;
const outPath   = path.join(destDir, fileName);
fs.writeFileSync(outPath, Buffer.from(await res.arrayBuffer()));

Technical Analysis

The output filename is derived from the remote server's Content-Disposition header. This value is attacker-controlled whenever an artifact endpoint is compromised or attacker-controlled. The script accepts path separators, traversal components, and potentially absolute-path syntax without sanitization.

path.join(destDir, fileName) normalizes path components but does not prove that the resulting path remains beneath destDir. A filename such as ../../.profile can therefore resolve outside ~/appian-exports/. The subsequent fs.writeFileSync() creates or truncates the resolved file without checking containment or preventing replacement of an existing file.

This behavior violates the documented constraint that artifacts are saved only under ~/appian-exports/. The write access needed for the declared functionality should be restricted to that directory.

Attack Path

  1. An attacker controls an artifact response used by downloadArtifact(). This can occur through a compromised Appian service or an attacker-controlled artifact URL.
  2. The response supplies downloaded content and a header such as: Content-Disposition: attachment; filename=../../.profile
  3. The user invokes --download-log or --download-zip.
  4. The script extracts the traversal string as fileName.
  5. path.join() resolves the destination outside ~/appian-exports/.
  6. fs.writeFileSync() creates or overwrites ...[truncated 845 chars]
Remediation
View remediation

Remediation Suggestions

  1. Treat the remote filename only as a display hint. Reduce it to a local filename with path.basename() after normalizing both POSIX and Windows separators.

  2. Reject empty names, ., .., path separators, absolute paths, drive prefixes, NUL bytes, and control characters.

  3. Resolve and verify containment before writing:

    js
    const root = path.resolve(destDir);
    const safeName = path.basename(fileName.replaceAll('\\', '/'));
    const outPath = path.resolve(root, safeName);
    
    if (path.dirname(outPath) !== root) {
        throw new Error('Unsafe artifact filename');
    }
    
  4. Generate a trusted local filename from deploymentUuid and the expected artifact type instead of trusting Content-Disposition.

  5. Avoid silently replacing existing files. Use exclusive creation, such as fs.writeFileSync(outPath, data, { flag: 'wx' }), or require explicit confirmation before overwriting.

  6. Consider rejecting symbolic-link destinations and opening files with platform-appropriate no-follow protections where available.

  7. Enforce reasonable download-size limits before buffering the complete response in memory and writing it to disk.

Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill advertises and documents access to environment variables and outbound network calls, but it does not declare an explicit tool scope such as permissions or allowed-tools. This creates a governance gap: an agent or reviewer cannot reliably constrain what the skill is permitted to access, which increases the chance of unintended secret exposure or unauthorized external requests, especially since the skill handles API credentials and may also fall back to a local appian.json file.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

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