Back to skill

Security audit

Dev Serve

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but its helper script can persistently modify development and Caddy configuration and contains input-handling flaws that could let crafted project names, domains, or ports change files or execute commands.

Review this before installing. Use it only on trusted repositories with simple DNS-safe folder names, a trusted DEV_SERVE_DOMAIN, and numeric ports. Expect it to edit your Caddyfile and Vite config files, and check diffs/backups before committing or reusing those changes. Avoid pointing CADDY_ADMIN anywhere except your intended local Caddy admin endpoint.

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

Error
Location
scripts/dev-serve.sh:265
Finding
Shell Command Injection Through the Unvalidated Port Argument<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dev-serve.sh`, lines 73-87 and 265-293 **Vulnerability Type**: OS command injection through shell command construction **Risk Level**: High ### Vulnerable Code ```bash detect_dev_cmd() { local repo="$1" local port="$2" # Check for override if [[ -n "${DEV_CMD:-}" ]]; then echo "$DEV_CMD" return fi # Detect package manager local pm="npm" if [[ -f "$repo/pnpm-lock.yaml" ]]; then pm="pnpm" elif [[ -f "$repo/bun.lockb" ]] || [[ -f "$repo/bun.lock" ]]; then pm="bun" elif [[ -f "$repo/yarn.lock" ]]; then pm="yarn" fi # Read dev script local dev_script dev_script=$(jq -r '.scripts.dev // empty' "$repo/package.json" 2>/dev/null) if [[ -z "$dev_script" ]]; then echo >&2 "Error: No 'dev' script in package.json. Set DEV_CMD env var." exit 1 fi # Check if it's a vite-based server (needs --host and --port flags) if echo "$dev_script" | grep -qiE '(vite|next|nuxt|svelte)'; then echo "$pm run dev -- --host 0.0.0.0 --port $port" else echo "PORT=$port $pm run dev" fi } ``` ```bash cmd_up() { local repo="${1:?missing repo path}" local repo_abs repo_abs=$(cd "$repo" 2>/dev/null && pwd) || { echo "Error: '$repo' not found" >&2; exit 1; } local name name=$(basename "$repo_abs") local port="${2:-$(next_port)}" # Check if already running if jq -e ".\"$name\"" "$STATE_FILE" >/dev/null 2>&1; then echo "Error: '$name' is already running. Use 'dev-serve down $name' first or 'dev-serve restart $name'." >&2 exit 1 fi local dev_cmd dev_cmd=$(detect_dev_cmd "$repo_abs" "$port") local subdomain="${name}.${DOMAIN}" echo "🚀 Starting ${name}" echo " Repo: ${repo_abs}" echo " Port: ${port}" echo " Command: ${dev_cmd}" echo " URL: https://${subdomain}" echo "" # Patch Vite allowedHosts if needed patch_vite_allowed_hosts "$repo_abs" "$subdomain" # Create tmux session with dev server local sessi ...[truncated 2335 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate an explicitly supplied port before using it anywhere: ```bash if [[ ! "$port" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then echo "Error: port must be an integer from 1 to 65535" >&2 exit 1 fi ``` 2. Do not submit assembled command strings to an interactive shell. Start tmux with an executable and separately quoted arguments where possible. 3. If shell interpretation is unavoidable, construct the command from trusted fixed tokens and escape every variable using `printf '%q'`. 4. Apply equivalent validation to auto-assigned and state-loaded ports. 5. Check that the selected port is not already listening, rather than checking only the state file and Caddyfile. 6. Add regression tests covering semicolons, command substitutions, whitespace, newlines, negative numbers, oversized values, and nonnumeric port arguments. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/dev-serve.sh:164
Finding
JavaScript and TypeScript Source Injection During Vite Configuration Patching<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dev-serve.sh`, lines 164-202 **Vulnerability Type**: Unsafe source-code generation and sed injection **Risk Level**: High ### Vulnerable Code ```bash patch_vite_allowed_hosts() { local repo="$1" local subdomain="$2" # Find vite config file local vite_config="" for candidate in "vite.config.ts" "vite.config.js" "vite.config.mts" "vite.config.mjs"; do if [[ -f "$repo/$candidate" ]]; then vite_config="$repo/$candidate" break fi done if [[ -z "$vite_config" ]]; then return 0 # Not a Vite project fi # Check if subdomain already in allowedHosts if grep -q "$subdomain" "$vite_config" 2>/dev/null; then echo " allowedHosts already includes ${subdomain}" return 0 fi # Check if allowedHosts array exists if grep -q "allowedHosts" "$vite_config" 2>/dev/null; then if [[ "$(uname)" == "Darwin" ]]; then sed -i '' "s/allowedHosts: \[/allowedHosts: ['${subdomain}', /" "$vite_config" else sed -i "s/allowedHosts: \[/allowedHosts: ['${subdomain}', /" "$vite_config" fi echo " ✅ Added ${subdomain} to allowedHosts in $(basename "$vite_config")" elif grep -q "server:" "$vite_config" 2>/dev/null; then if [[ "$(uname)" == "Darwin" ]]; then sed -i '' "/server:.*{/a\\ \\ allowedHosts: ['${subdomain}'], " "$vite_config" else sed -i "/server:.*{/a\\ allowedHosts: ['${subdomain}']," "$vite_config" fi echo " ✅ Added allowedHosts with ${subdomain} to $(basename "$vite_config")" else echo " ⚠️ Could not auto-patch allowedHosts. Add manually to $(basename "$vite_config"):" echo " server: { allowedHosts: ['${subdomain}'] }" fi } ``` The value passed as `subdomain` is constructed from unvalidated inputs: ```bash name=$(basename "$repo_abs") local subdomain="${name}.${DOMAIN}" ``` ### Technical Analysis The repository directory basename and `DEV_SERVE_DOMAIN` are used to construct `s ...[truncated 2343 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate the repository basename as a strict DNS label before constructing the subdomain. For example, permit only lowercase ASCII letters, digits, and internal hyphens, enforce a maximum label length of 63 characters, and reject leading or trailing hyphens. 2. Validate `DEV_SERVE_DOMAIN` as a sequence of valid DNS labels. Normalize case and reject quotes, whitespace, control characters, slashes, backslashes, and newlines. 3. Do not modify executable configuration using regular-expression substitutions. Use a syntax-aware JavaScript/TypeScript parser and printer, or require the user to configure `allowedHosts` manually. 4. If source generation remains necessary, serialize values using a trusted JavaScript string serializer rather than interpolating them into quotes. 5. Escape values independently for every context in which they are used; sed-pattern escaping, sed-replacement escaping, and JavaScript string escaping are different operations. 6. Create a backup, write to a temporary file in the same directory, validate the generated configuration, and atomically replace the original only after validation succeeds. 7. Prompt for explicit confirmation before modifying repository source, and provide an automatic rollback or removal operation. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/dev-serve.sh:107
Finding
Caddy Configuration Injection Through Unvalidated Repository and Domain Names<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dev-serve.sh`, lines 107-143 and 151-160 **Vulnerability Type**: Configuration injection into a security-sensitive reverse proxy **Risk Level**: High ### Vulnerable Code ```bash add_caddy_route() { local name="$1" local port="$2" local subdomain="${name}.${DOMAIN}" # Check if route already exists if grep -q "${subdomain}" "$CADDYFILE"; then echo " Caddy route for ${subdomain} already exists, updating port..." # Update the port in existing route if [[ "$(uname)" == "Darwin" ]]; then sed -i '' "/${subdomain}/,/^}/s/localhost:[0-9]*/localhost:${port}/" "$CADDYFILE" else sed -i "/${subdomain}/,/^}/s/localhost:[0-9]*/localhost:${port}/" "$CADDYFILE" fi else # Add new route block cat >> "$CADDYFILE" <<EOF # ${name} (dev-serve) ${subdomain} { import vercel_tls reverse_proxy localhost:${port} } EOF # Add to dashboard HTML if there's a </ul> tag if grep -q '</ul>' "$CADDYFILE"; then local dashboard_entry="<li><a href=\"https://${subdomain}\">${name}<div class=\"desc\">Dev Server</div></a></li>" if [[ "$(uname)" == "Darwin" ]]; then sed -i '' "s|</ul>|${dashboard_entry}\n\t</ul>|" "$CADDYFILE" else sed -i "s|</ul>|${dashboard_entry}\n\t</ul>|" "$CADDYFILE" fi fi fi } ``` The generated configuration is then submitted to Caddy: ```bash reload_caddy() { echo " Reloading Caddy..." if curl -sf -X POST "${CADDY_ADMIN}/load" \ -H "Content-Type: text/caddyfile" \ --data-binary "@${CADDYFILE}" >/dev/null 2>&1; then echo " ✅ Caddy reloaded" else echo " ⚠️ Caddy API reload failed. Try: caddy reload --config ${CADDYFILE} --address localhost:2019" fi } ``` ### Technical Analysis The generated Caddyfile embeds `name`, `DOMAIN`, and `port` without validating or escaping them for Caddyfile syntax. The repository name is derived from an arbitrary directory basename, while the domain ...[truncated 2183 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply strict DNS-label validation to repository-derived names and strict fully qualified domain validation to `DEV_SERVE_DOMAIN`. 2. Require the port to be a decimal integer in the range `1-65535`. 3. Reject all control characters, whitespace, newlines, braces, quotes, backslashes, and Caddyfile metacharacters in generated identifiers. 4. Prefer Caddy's structured JSON administration API over generating Caddyfile text. Build a fixed route object and place validated values only in data fields. 5. If a Caddyfile must be maintained, generate the complete managed block in a temporary file, run Caddy configuration validation, and atomically replace the original only after validation succeeds. 6. Keep managed routes in a dedicated imported file rather than editing the primary Caddyfile and arbitrary dashboard markup. 7. Mark managed blocks with a random or fixed internal identifier rather than locating them through unescaped regular expressions. 8. Escape any remaining grep or sed input appropriately, or replace those operations with exact structured parsing. 9. Preserve a backup and automatically restore it if validation or API reload fails. 10. Restrict the Caddy administration endpoint to loopback or another authenticated local channel and avoid permitting untrusted overrides of `CADDY_ADMIN`. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Ae1

High
Category
analysis-evasion
Content
cp scripts/dev-serve.sh ~/.local/bin/dev-serve
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill clearly directs the agent/user to perform shell-capable actions such as copying executables, changing permissions, starting tmux sessions, editing config files, and reloading Caddy, yet it declares no explicit tool scope or permissions metadata. This increases the risk of overbroad agent execution because consumers cannot easily tell that the skill may modify files, launch network services, and interact with the local reverse proxy/admin API.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill notes later that it auto-patches Vite allowedHosts, but the primary description and command behavior do not prominently warn that running `dev-serve up` will modify repository source/config files. Silent or easy-to-miss file modification is security-relevant because it can alter application behavior, create unexpected diffs, and normalize agent-driven code changes in a repo the user may not intend to edit.

External Transmission

Medium
Category
Data Exfiltration
Content
# Reload Caddy via admin API
reload_caddy() {
  echo "  Reloading Caddy..."
  if curl -sf -X POST "${CADDY_ADMIN}/load" \
    -H "Content-Type: text/caddyfile" \
    --data-binary "@${CADDYFILE}" >/dev/null 2>&1; then
    echo "  ✅ Caddy reloaded"
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The script edits files inside the target repository by automatically patching Vite configuration, which exceeds the expected boundary of a tool described as starting dev servers and configuring Caddy. This can silently alter application source/configuration, break builds, create persistent changes that may later be committed, and weakens host validation by expanding allowedHosts without explicit user approval.

Static analysis

No suspicious patterns detected.