Back to skill

Security audit

Static App

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed Static.app management tool, but it combines deployment with destructive account actions and unsafe credential/download handling that users should review before installing.

Install only if you intend to give this skill a Static.app API key with authority to list, update, download, and delete sites. Prefer STATIC_APP_API_KEY over -k/--api-key, avoid --force deletion unless you have independently verified the PID, and treat downloaded archives/output paths as sensitive until the dependency and path-validation issues are fixed.

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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/deploy.js:18
Finding
API Keys Can Be Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/deploy.js:18-33`; equivalent behavior appears in `scripts/download.js:17-28`, `scripts/files.js:11-22`, `scripts/list.js:11-21`, and `scripts/delete.js:12-22`. The `-k` option is documented in `SKILL.md:115-139` and `SKILL.md:167`. **Vulnerability Type**: Credential exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```js function parseArgs() { const args = process.argv.slice(2); const options = { sourceDir: '.', apiKey: process.env.STATIC_APP_API_KEY, pid: null, exclude: null, keepZip: false }; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '-k' || arg === '--api-key') { options.apiKey = args[++i]; ``` The same command-line credential pattern is implemented by every API utility. The documentation explicitly presents it as a supported option: ```markdown Options: - `--raw` — Output raw JSON - `-k <key>` — Specify API key ``` ### Technical Analysis Static.app API keys are bearer credentials capable of authenticating account operations. Accepting the key through `-k` or `--api-key` places it in `process.argv`. Depending on the host configuration, command-line arguments may be observable through: - Process inspection utilities and `/proc` metadata. - Shell history. - Agent tool-call and execution logs. - Terminal session recording. - Endpoint monitoring and process auditing products. - Wrapper scripts that record invoked commands. The scripts also support `STATIC_APP_API_KEY`, which is safer than a command-line parameter in many environments, but the insecure alternative remains enabled and documented. The behavior is unnecessary for the declared functionality because authentication can be supplied through a secret manager, protected environment injection, standard input, or a restricted credential file. ### Attack Path 1. A user or AI agent invokes a utility with a credential, for example: ``` ...[truncated 1119 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `-k` and `--api-key` command-line options from all scripts. 2. Obtain the credential from a secret manager or a protected environment variable injected only into the child process. 3. If interactive entry is required, read the key from a hidden prompt or standard input without echoing it. 4. Optionally support a credential file, but require restrictive permissions and reject files readable by other users. 5. Update `SKILL.md` so examples never place keys directly in commands. 6. Add a warning and migration period if backward compatibility requires temporary support for `--api-key`. 7. Ensure agent execution logs, errors, and telemetry redact strings matching the Static.app key format. 8. Use narrowly scoped and short-lived API credentials where supported, and rotate any keys previously supplied through command-line arguments. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download.js:193
Finding
Unvalidated PID Influences Download, Extraction, and Cleanup Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download.js:17-38,193-203` **Vulnerability Type**: Path traversal and unsafe temporary-file construction **Risk Level**: Medium ### Vulnerable Code The PID is accepted directly from command-line input without format validation: ```js function parseArgs() { const args = process.argv.slice(2); const options = { apiKey: process.env.STATIC_APP_API_KEY, pid: null, outputDir: null, raw: false }; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '-k' || arg === '--api-key') { options.apiKey = args[++i]; } else if (arg === '-p' || arg === '--pid') { options.pid = args[++i]; } else if (arg === '-o' || arg === '--output') { options.outputDir = args[++i]; } else if (arg === '--raw') { options.raw = true; } else if (arg === '-h' || arg === '--help') { showHelp(); process.exit(0); } else if (!arg.startsWith('-') && !options.pid) { options.pid = arg; } } ``` It is subsequently incorporated into filesystem paths: ```js // Step 2: Determine output directory const outputDir = options.outputDir || path.join(WORKSPACE_DIR, options.pid); const zipPath = path.join(process.cwd(), `${options.pid}-download.zip`); // Step 3: Download the file await downloadFile(downloadUrl, zipPath); // Step 4: Extract extractZip(zipPath, outputDir); // Step 5: Clean up zip file fs.unlinkSync(zipPath); ``` ### Technical Analysis `options.pid` is treated both as a remote identifier and as a local path component. No allowlist, separator rejection, basename normalization, or resolved-path containment check is applied. Values containing path traversal components can cause `path.join()` to resolve outside the intended working or workspace directory. The temporary archive destination is opened with `fs.createWriteStream()` in `downloadFile()`, which can create or truncate the resolved file. After extraction, `fs.un ...[truncated 2194 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate PIDs against the exact identifier syntax documented by Static.app. Use a strict allowlist, for example letters and digits with an explicit maximum length, if that matches the service specification. 2. Reject `/`, `\`, null bytes, empty values, absolute paths, `.` components, and `..` components. 3. Generate temporary archive names independently of the PID using `fs.mkdtemp()` and a fixed filename inside the newly created directory. 4. Resolve the default destination with `path.resolve()` and verify that it remains beneath the approved workspace root: ```js const root = path.resolve(WORKSPACE_DIR); const destination = path.resolve(root, validatedPid); if (destination !== root && !destination.startsWith(root + path.sep)) { throw new Error('Destination escapes workspace'); } ``` 5. Perform an equivalent containment check before every write, extraction, and deletion. 6. Use `try`/`finally` for cleanup and only delete temporary files created by the current invocation. 7. In agent-controlled execution, require explicit confirmation before honoring a custom `--output` destination outside the normal workspace. 8. Add automated tests for absolute paths, traversal sequences, mixed separators, encoded separators, long identifiers, and symlinked parent directories. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download.js:92
Finding
Remote Download URL Is Followed Without Destination or Resource Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download.js:72-112,120-125,183-199` **Vulnerability Type**: Unrestricted remote fetch and unbounded archive extraction **Risk Level**: Medium ### Vulnerable Code The API response is trusted as a download destination: ```js async function getDownloadUrl(apiKey, pid) { const url = `${API_BASE}/${pid}`; console.log(`📥 Fetching download URL for site: ${pid}\n`); const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${apiKey}`, 'Accept': 'application/json' } }); if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); } return await response.json(); } async function downloadFile(url, destPath) { console.log(`⬇️ Downloading from ${url}...`); const response = await fetch(url, { redirect: 'follow' }); if (!response.ok) { throw new Error(`HTTP ${response.status}: Failed to download file`); } const fileStream = fs.createWriteStream(destPath); return new Promise((resolve, reject) => { response.body.pipe(fileStream); response.body.on('error', reject); fileStream.on('finish', () => { const stats = fs.statSync(destPath); console.log(`💾 Downloaded: ${formatBytes(stats.size)}`); resolve(); }); }); } ``` The downloaded content is extracted without size or entry limits: ```js function extractZip(zipPath, extractPath) { console.log(`📦 Extracting to ${extractPath}...`); // Ensure extract directory exists if (!fs.existsSync(extractPath)) { fs.mkdirSync(extractPath, { recursive: true }); } // Extract using adm-zip (pure Node.js, no shell commands) try { const zip = new AdmZip(zipPath); zip.extractAllTo(extractPath, true); console.log(`✅ Extracted successfully`); } catch (err) { console.error(`❌ Extraction failed: ${err.message}`); throw new Error('Extrac ...[truncated 3505 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `https:` for every download URL. 2. Maintain an allowlist of documented Static.app archive/CDN hostnames. 3. Validate every redirect target rather than relying on unrestricted `redirect: 'follow'`. 4. Resolve destination hostnames and reject loopback, private, link-local, multicast, and metadata-service address ranges unless explicitly required. 5. Add connection, response, and total transfer timeouts with `AbortController`. 6. Enforce a maximum compressed download size while streaming; do not rely solely on `Content-Length`. 7. Validate the response content type and ZIP signature before extraction. 8. Inspect archive metadata before extraction and reject archives exceeding limits for: - Entry count. - Per-entry expanded size. - Total expanded size. - Compression ratio. - Filename length and nesting depth. 9. Extract into a fresh temporary directory with a disk quota where supported, then atomically move validated content into the destination. 10. Delete partial downloads on stream errors and handle both stream and destination-file errors. 11. Log only a redacted or normalized URL so signed query credentials are not exposed in agent logs. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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
Findings (43)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill also enumerates all sites via the API, which is broader account-management functionality than simple deployment. This mismatch can expose account inventory and enable unintended operational actions because users may invoke the skill assuming it only uploads local content.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill also enumerates all sites via the API, which is broader account-management functionality than simple deployment. This mismatch can expose account inventory and enable unintended operational actions because users may invoke the skill assuming it only uploads local content.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill also enumerates all sites via the API, which is broader account-management functionality than simple deployment. This mismatch can expose account inventory and enable unintended operational actions because users may invoke the skill assuming it only uploads local content.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill also enumerates all sites via the API, which is broader account-management functionality than simple deployment. This mismatch can expose account inventory and enable unintended operational actions because users may invoke the skill assuming it only uploads local content.

Ae1

High
Category
analysis-evasion
Content
node scripts/deploy.js ./dist
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deploy.js ./dist
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deploy.js ./dist
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deploy.js ./dist
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deploy.js ./dist
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/deploy.js ./dist
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/list.js
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/download.js YOUR_PID
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/download.js YOUR_PID
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/download.js YOUR_PID
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The script performs irreversible site deletion even though the skill is described as handling deployment, upload, or hosting tasks. This scope mismatch is dangerous because users or higher-level agents invoking the skill for benign hosting workflows may unknowingly gain destructive capability, increasing the risk of accidental or unauthorized deletion.

Credential Access

High
Category
Privilege Escalation
Content
const fetch = require('node-fetch');

const API_URL = 'https://api.static.app/v1/sites/zip/';
const DEFAULT_EXCLUDE = ['node_modules', '.git', '.github', '*.md', 'package*.json', '.env', '.openclaw'];

function parseArgs() {
  const args = process.argv.slice(2);
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Known Vulnerable Dependency: adm-zip==0.5.16 — 2 advisory(ies): CVE-2026-76845 (adm-zip extraction follows destination symlinks, allowing arbitrary file overwri); CVE-2026-39244 (adm-zip: Crafted ZIP file triggers 4GB memory allocation)

High
Category
Supply Chain
Confidence
97% confidence
Finding
The lockfile pins adm-zip 0.5.16, which is reported vulnerable to unsafe ZIP handling, including following destination symlinks during extraction and a crafted-archive memory exhaustion condition. In a static-site deployment skill that processes archives, this is directly relevant because attacker-controlled ZIP content could lead to arbitrary file overwrite on the host or denial of service during packaging/extraction workflows.

Known Vulnerable Dependency: brace-expansion==2.0.2 — 4 advisory(ies): CVE-2026-13149 (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding); CVE-2026-33750 (brace-expansion: Zero-step sequence causes process hang and memory exhaustion); CVE-2026-14257 (brace-expansion: DoS via unbounded expansion length causing an out-of-memory pro) +1 more

High
Category
Supply Chain
Confidence
82% confidence
Finding
brace-expansion 2.0.2 is flagged for denial-of-service issues caused by pathological brace patterns that trigger excessive expansion, hanging, or memory exhaustion. This is a transitive dependency used for pattern parsing; it becomes dangerous if the skill accepts attacker-controlled glob or pattern input, though its exploitability is less direct than the archive-handling issues.

Known Vulnerable Dependency: form-data==4.0.5 — 1 advisory(ies): CVE-2026-12143 (form-data: CRLF injection in form-data via unescaped multipart field names and f)

High
Category
Supply Chain
Confidence
92% confidence
Finding
form-data 4.0.5 is flagged for CRLF injection through unescaped multipart field names and filenames. In a deployment skill that uploads artifacts to a hosting service, this is particularly relevant because attacker-controlled metadata could tamper with multipart HTTP requests, potentially altering request structure, smuggling unintended fields, or confusing upstream services.

Known Vulnerable Dependency: minimatch==9.0.5 — 3 advisory(ies): CVE-2026-27904 (minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regu); CVE-2026-26996 (minimatch has a ReDoS via repeated wildcards with non-matching literal in patter); CVE-2026-27903 (minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adja)

High
Category
Supply Chain
Confidence
80% confidence
Finding
minimatch 9.0.5 is flagged for multiple ReDoS issues involving crafted glob patterns that cause catastrophic backtracking or combinatorial matching behavior. In this skill, such risk matters if file selection or ignore/include patterns can be influenced by users or project contents, potentially allowing a deployment operation to hang or consume excessive CPU.

Known Vulnerable Dependency: lodash==4.17.23 — 2 advisory(ies): CVE-2025-13465 (lodash vulnerable to Prototype Pollution via array path bypass in `_.unset` and ); CVE-2021-23337 (lodash vulnerable to Code Injection via `_.template` imports key names)

High
Category
Supply Chain
Confidence
80% confidence
Finding
lodash 4.17.23 is reported with prototype pollution and template/code-injection advisories, but exploitability depends heavily on whether the skill invokes affected functions such as _.unset on attacker-controlled paths or _.template on untrusted input. The lockfile alone does not prove those code paths are used, yet keeping a version with known high-severity issues in a deployment tool is still risky because transitive code may expose them.

Known Vulnerable Dependency: minimatch==5.1.6 — 3 advisory(ies): CVE-2026-27904 (minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regu); CVE-2026-26996 (minimatch has a ReDoS via repeated wildcards with non-matching literal in patter); CVE-2026-27903 (minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adja)

High
Category
Supply Chain
Confidence
84% confidence
Finding
A second vulnerable minimatch version, 5.1.6, is present transitively and carries the same ReDoS class of weaknesses as the newer flagged copy. Because deployment tools often walk directories and evaluate ignore/include patterns, attacker-influenced patterns or repository contents could trigger CPU exhaustion and stall deployments.

Known Vulnerable Dependency: adm-zip==0.5.16 — 2 advisory(ies): CVE-2026-76845 (adm-zip extraction follows destination symlinks, allowing arbitrary file overwri); CVE-2026-39244 (adm-zip: Crafted ZIP file triggers 4GB memory allocation)

High
Category
Supply Chain
Confidence
97% confidence
Finding
The manifest includes adm-zip 0.5.16, which is flagged with high-severity issues including symlink-following arbitrary overwrite during extraction and a crafted ZIP memory-allocation denial of service. In a deployment skill that handles site packaging or archives, ZIP processing is contextually relevant, so a vulnerable archive library materially increases risk if untrusted or malformed archives are ever accepted or processed.

Known Vulnerable Dependency: form-data==4.0.5 — 1 advisory(ies): CVE-2026-12143 (form-data: CRLF injection in form-data via unescaped multipart field names and f)

High
Category
Supply Chain
Confidence
94% confidence
Finding
The resolved form-data version is reported vulnerable to CRLF injection via unescaped multipart field names/filenames. This skill uploads deployment artifacts to a hosting service, so malformed user-controlled metadata in multipart requests could potentially tamper with HTTP request structure or headers if such fields are influenced by external input.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documents use of environment variables and outbound network access to a third-party API, but the manifest declares no explicit tool scope or permissions. That gap reduces transparency and control for reviewers and execution frameworks, making it easier for a deployment-oriented skill to access secrets and send data externally without clearly bounded authorization.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/delete.js:13

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/deploy.js:20

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/download.js:19

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/files.js:13

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/list.js:13