# Scan Vuln

> Security vulnerability validation — comprehensive vulnerability assessment, security control testing, and compliance verification using systematic assessment methodologies. Invoke via /scan-vuln [target].

- **Type:** Skill
- **Install:** `agentstack add skill-thapr0digy-skills-scan-vuln`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [thapr0digy](https://agentstack.voostack.com/s/thapr0digy)
- **Installs:** 0
- **Category:** [Security](https://agentstack.voostack.com/c/security)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [thapr0digy](https://github.com/thapr0digy)
- **Source:** https://github.com/thapr0digy/skills/tree/main/plugins/pentest-scanning/skills/scan-vuln

## Install

```sh
agentstack add skill-thapr0digy-skills-scan-vuln
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

@pentest-core/skills/shared/engagement-resolver.md
@pentest-core/skills/shared/scope-validator.md
@pentest-core/skills/shared/remote-exec.md
@pentest-core/skills/shared/activity-log.md

# /scan-vuln — Security Vulnerability Validation

You are conducting systematic vulnerability assessment to identify security weaknesses, validate security controls, and ensure compliance with security standards. You use comprehensive scanning methodologies combined with manual validation techniques to provide accurate vulnerability assessment results for security improvement planning.

---

## Step 1: Resolve Active Engagement

Run the engagement resolver block verbatim before any other logic. Also read `custom_templates_dir` from the engagement for use in Step 4.

```bash
# --- Engagement Resolver ---
ENGAGEMENT_JSON=$(cat ~/.pentest/active-engagement 2>/dev/null)

if [ -z "$ENGAGEMENT_JSON" ]; then
  echo "No active engagement found. Run /pentest-init or /pentest-switch." >&2
  exit 1
fi

if [ ! -f "$ENGAGEMENT_JSON" ]; then
  echo "Active engagement path '$ENGAGEMENT_JSON' does not exist. Run /pentest-switch." >&2
  exit 1
fi

ENGAGEMENT_ID=$(jq -r '.engagement_id' "$ENGAGEMENT_JSON")
OUTPUT_DIR=$(jq -r '.output_dir' "$ENGAGEMENT_JSON")
ASSESSOR=$(whoami)

ASSESSOR_MATCH=$(jq -r --arg h "$ASSESSOR" '.security assessors[] | select(.handle == $h) | .handle' "$ENGAGEMENT_JSON")
if [ -z "$ASSESSOR_MATCH" ]; then
  echo "Operator '$ASSESSOR' is not registered on this engagement." >&2
  exit 1
fi

CUSTOM_TEMPLATES_DIR=$(jq -r '.custom_templates_dir // empty' "$ENGAGEMENT_JSON")
# --- End Engagement Resolver ---
```

After this block, `$ENGAGEMENT_ID`, `$OUTPUT_DIR`, `$ASSESSOR`, and optionally `$CUSTOM_TEMPLATES_DIR` are set and valid. If the resolver fails for any reason, stop and tell the user to run `/pentest-init`.

---

## Step 2: Parse Arguments and Determine Targets

### Accepted input formats

- **IP address** — e.g., `10.0.0.1`
- **URL** — e.g., `https://app.acme.com`
- **CIDR range** — e.g., `10.0.0.0/24`
- **File path** — e.g., `@/path/to/targets.txt` — one target per line

```bash
ARG="${1:-}"

if [ -n "$ARG" ]; then
  if echo "$ARG" | grep -q '^@'; then
    TARGETS_FILE="${ARG#@}"
    if [ ! -f "$TARGETS_FILE" ]; then
      echo "Targets file not found: $TARGETS_FILE" >&2
      exit 1
    fi
    TARGET_IS_FILE=true
  else
    # Write single target to a temp file for nuclei -l flag
    TARGETS_FILE=$(mktemp)
    echo "$ARG" > "$TARGETS_FILE"
    TARGET_IS_FILE=false
  fi
else
  # Build target list from recon and enum outputs
  TARGETS_FILE="${OUTPUT_DIR}/findings/scan-vuln-targets-${ASSESSOR}-$(date +%s).txt"

  # Pull hosts from active recon profiles
  for f in "${OUTPUT_DIR}"/recon/active-*.json; do
    [ -f "$f" ] && jq -r '
      .hosts[] |
      .ip as $ip |
      .ports[] |
      select(.state == "open") |
      if .category == "web" then
        ($ip + ":" + (.port | tostring))
      else
        $ip
      end
    ' "$f" 2>/dev/null
  done >> "$TARGETS_FILE"

  # Pull live URLs from web enumeration outputs
  for f in "${OUTPUT_DIR}"/enum/web-*.json; do
    [ -f "$f" ] && jq -r '.live_urls[]? // empty' "$f" 2>/dev/null
  done >> "$TARGETS_FILE"

  sort -u -o "$TARGETS_FILE" "$TARGETS_FILE"

  if [ ! -s "$TARGETS_FILE" ]; then
    echo "No target provided and no recon/enum data found." >&2
    echo "Provide a target: /scan-vuln " >&2
    echo "Or run /recon-active and /enum-web first." >&2
    exit 1
  fi

  echo "No target specified — built target list from recon/enum data: $TARGETS_FILE"
fi

TARGET_COUNT=$(wc -l /dev/null)
HOST_NAME="${HOST_NAME:-${DEFAULT_HOST:-local}}"
```

### Full scope validation

For each target in `$TARGETS_FILE`, run complete scope validation per `scope-validator.md`. **Perform all checks including the testing window check** — nuclei sends active probes and must respect testing windows. Hard-block out-of-scope targets; warn and confirm for unlisted targets. Do not batch-validate — check each target individually.

### Scan mode selection

Ask the user:

> **Scan mode:**
> 1. **Quick** — critical and high severity templates only, runs faster (~5–15 min per 10 hosts)
> 2. **Comprehensive** — all severity levels, full template coverage (slower, recommended for final assessments)
>
> Which mode? (1/2, default: 1)

Store as `SCAN_MODE` (`quick` or `comprehensive`).

```bash
mkdir -p "${OUTPUT_DIR}/findings"
TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ")
```

---

## Step 3: Check Tool Availability

Nuclei is required. If it is not available, abort.

```bash
if ! which nuclei > /dev/null 2>&1; then
  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "tool_check" \
    --arg command "which nuclei" \
    --arg status "failed" \
    --arg result "not found" \
    '{ts:$ts, security assessor:$security assessor, action:$action, command:$command, status:$status, result:$result}' \
    >> "${OUTPUT_DIR}/activity.log"
  echo "nuclei not found. Install from https://github.com/projectdiscovery/nuclei or via: go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest" >&2
  exit 1
fi

NUCLEI_VERSION=$(nuclei -version 2>&1 | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1)

jq -nc \
  --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
  --arg security assessor "$ASSESSOR" \
  --arg action "tool_check" \
  --arg command "exit 1" \
  --arg status "completed" \
  --arg result "available: $NUCLEI_VERSION" \
  '{ts:$ts, security assessor:$security assessor, action:$action, command:$command, status:$status, result:$result}' \
  >> "${OUTPUT_DIR}/activity.log"
```

Offer to update templates:

> nuclei `$NUCLEI_VERSION` found. Update nuclei templates before scanning? (yes/no, default: yes)

If yes (or default):

```bash
nuclei -update-templates -silent
```

---

## Step 4: Build Template Selection

Read tech stack data from recon outputs to select the most relevant nuclei template paths. Only load templates you have evidence for — do not throw every available template at every target.

### Always-on template categories

These run regardless of discovered tech stack:

```bash
TEMPLATE_ARGS="-t cves/ -t misconfiguration/ -t exposures/"
```

### Tech-stack-aware templates

Read httpx and whatweb outputs to determine which technologies are present:

```bash
DETECTED_TECH=""

for f in "${OUTPUT_DIR}"/recon/httpx-*.json; do
  [ -f "$f" ] && DETECTED_TECH="$DETECTED_TECH $(jq -r '.tech[]? // .technologies[]? // empty' "$f" 2>/dev/null | tr '[:upper:]' '[:lower:]')"
done

for f in "${OUTPUT_DIR}"/recon/whatweb-*.json; do
  [ -f "$f" ] && DETECTED_TECH="$DETECTED_TECH $(jq -r '.[].plugins | keys[]' "$f" 2>/dev/null | tr '[:upper:]' '[:lower:]')"
done

DETECTED_TECH=$(echo "$DETECTED_TECH" | tr ' ' '\n' | sort -u | tr '\n' ' ')
```

For each technology present in `$DETECTED_TECH`, add the corresponding template path:

| Technology keyword(s) | Template path to add |
|---|---|
| `apache` | `-t technologies/apache/` |
| `nginx` | `-t technologies/nginx/` |
| `wordpress` | `-t technologies/wordpress/` |
| `joomla` | `-t technologies/joomla/` |
| `drupal` | `-t technologies/drupal/` |
| `php` | `-t technologies/php/` |
| `java`, `spring`, `springframework` | `-t technologies/java/ -t technologies/spring/` |
| `asp.net`, `aspnet` | `-t technologies/aspnet/` |
| `node`, `nodejs`, `express` | `-t technologies/nodejs/` |
| `iis` | `-t technologies/iis/` |
| `tomcat` | `-t technologies/tomcat/` |
| `jenkins` | `-t technologies/jenkins/` |
| `gitlab` | `-t technologies/gitlab/` |
| `grafana` | `-t technologies/grafana/` |

```bash
# Example detection and conditional addition — repeat for each technology
if echo "$DETECTED_TECH" | grep -qi "apache"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/apache/"
fi
if echo "$DETECTED_TECH" | grep -qi "nginx"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/nginx/"
fi
if echo "$DETECTED_TECH" | grep -qiE "wordpress|wp-"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/wordpress/"
fi
if echo "$DETECTED_TECH" | grep -qi "joomla"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/joomla/"
fi
if echo "$DETECTED_TECH" | grep -qi "drupal"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/drupal/"
fi
if echo "$DETECTED_TECH" | grep -qi "php"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/php/"
fi
if echo "$DETECTED_TECH" | grep -qiE "java|spring"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/java/ -t technologies/spring/"
fi
if echo "$DETECTED_TECH" | grep -qiE "asp\.net|aspnet"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/aspnet/"
fi
if echo "$DETECTED_TECH" | grep -qiE "node|nodejs|express"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/nodejs/"
fi
if echo "$DETECTED_TECH" | grep -qi "iis"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/iis/"
fi
if echo "$DETECTED_TECH" | grep -qi "tomcat"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/tomcat/"
fi
if echo "$DETECTED_TECH" | grep -qi "jenkins"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/jenkins/"
fi
if echo "$DETECTED_TECH" | grep -qi "gitlab"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/gitlab/"
fi
if echo "$DETECTED_TECH" | grep -qi "grafana"; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t technologies/grafana/"
fi
```

### Custom templates

If `$CUSTOM_TEMPLATES_DIR` is set and the directory exists, add it:

```bash
if [ -n "$CUSTOM_TEMPLATES_DIR" ] && [ -d "$CUSTOM_TEMPLATES_DIR" ]; then
  TEMPLATE_ARGS="$TEMPLATE_ARGS -t ${CUSTOM_TEMPLATES_DIR}"
fi
```

### Severity filter

```bash
if [ "$SCAN_MODE" = "quick" ]; then
  SEVERITY_FLAG="-severity critical,high"
else
  SEVERITY_FLAG=""
fi
```

---

## Step 5: Handle Authentication

If the targets include authenticated web applications, ask the user:

> **Authentication** — does any target require authentication to scan effectively?
> 1. Cookie (paste your session cookie value)
> 2. Bearer token (paste your token)
> 3. Basic auth (format: `username:password`)
> 4. No authentication needed

For each credential provided, store as a header flag:

```bash
AUTH_HEADERS=""

# Cookie
# AUTH_HEADERS="-H 'Cookie: session='"

# Bearer token
# AUTH_HEADERS="-H 'Authorization: Bearer '"

# Basic auth
# AUTH_HEADERS="-H 'Authorization: Basic '"
```

If multiple auth types are needed (different credentials per target), ask the user which targets each set of credentials applies to, and plan to run nuclei once per credential set against the appropriate target subset.

Log the auth method used (not the credential value itself):

```bash
jq -nc \
  --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
  --arg security assessor "$ASSESSOR" \
  --arg action "scan" \
  --arg command "nuclei auth configuration: ${AUTH_METHOD:-none}" \
  --arg status "dispatched" \
  --arg result "auth configured: ${AUTH_METHOD:-none}" \
  '{ts:$ts, security assessor:$security assessor, action:$action, command:$command, status:$status, result:$result}' \
  >> "${OUTPUT_DIR}/activity.log"
```

---

## Step 6: Execute Nuclei

### Rate limit selection

Check the engagement RoE for rate-limit guidance. Ask the user:

> **Rate limiting:**
> 1. Default (150 req/s) — suitable for most external assessments
> 2. Conservative (50 req/s) — recommended for red team ops, fragile targets, or when RoE restricts load
> 3. Custom — enter requests per second
>
> Which setting? (1/2/3, default: 1)

For red team engagements (`type: "red_team"` in `engagement.json`), default to conservative:

```bash
ENGAGEMENT_TYPE=$(jq -r '.type' "$ENGAGEMENT_JSON")
if [ "$ENGAGEMENT_TYPE" = "red_team" ]; then
  RATE_LIMIT=50
else
  RATE_LIMIT=150
fi
# Override with user selection
```

### Output path

```bash
NUCLEI_RAW="${OUTPUT_DIR}/findings/nuclei-raw-${ASSESSOR}-$(date +%s).jsonl"
```

### Execution command

```bash
CMD="nuclei -l ${TARGETS_FILE} ${TEMPLATE_ARGS} ${SEVERITY_FLAG} ${AUTH_HEADERS} -jsonl -o ${NUCLEI_RAW} -silent -rate-limit ${RATE_LIMIT} -bulk-size 25 -concurrency 10"
```

### Dispatch strategy based on target count

```bash
TARGET_COUNT=$(wc -l > "${OUTPUT_DIR}/activity.log"
```

**Fewer than 10 targets** — run synchronously:

```bash
$CMD

jq -nc \
  --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
  --arg security assessor "$ASSESSOR" \
  --arg action "scan" \
  --arg command "$CMD" \
  --arg status "$([ $? -eq 0 ] && echo completed || echo failed)" \
  --arg output_file "$NUCLEI_RAW" \
  '{ts:$ts, security assessor:$security assessor, action:$action, command:$command,
    status:$status, output_file:$output_file}' \
  >> "${OUTPUT_DIR}/activity.log"
```

**10–50 targets** — run synchronously with a warning to the user:

> Scanning `$TARGET_COUNT` targets synchronously. This may take 15–60 minutes. Do not close this session.

**50+ targets** — dispatch asynchronously using the remote execution abstraction from `remote-exec.md`. Use `$HOST_NAME` to determine SSH, SSM, or local tmux dispatch:

```bash
SESSION_NAME="${ENGAGEMENT_ID}-nuclei-$(date +%s)"

# Dispatch per remote-exec.md — SSH, SSM, or local tmux based on $HOST_NAME
# For local execution:
tmux new-session -d -s "$SESSION_NAME" "$CMD"

jq -nc \
  --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
  --arg security assessor "$ASSESSOR" \
  --arg action "scan" \
  --arg command "$CMD" \
  --arg status "dispatched" \
  --arg session "$SESSION_NAME" \
  --arg remote_host "$HOST_NAME" \
  --arg output_file "$NUCLEI_RAW" \
  '{ts:$ts, security assessor:$security assessor, action:$action,     command:$command, status:$status, session:$session,
    remote_host:$remote_host, output_file:$output_file}' \
  >> "${OUTPUT_DIR}/activity.log"
```

Tell the user:

> Nuclei dispatched in tmux session `$SESSION_NAME` (remote: `$HOST_NAME`).
> Output: `$NUCLEI_RAW`
> Check progress: `tmux attach -t $SESSION_NAME`
>
> The skill will now wait for the output file to appear, then continue with parsing. You will be notified when parsing begins.

Wait for `$NUCLEI_RAW` to exist and stop growing before proceeding to Step 7.

---

## Step 7: Parse Nuclei Output

Read `$NUCLEI_RAW` line by line. Each line is a JSON object from nuclei's `-jsonl` output. Map each line to the standard finding schema.

### Field mapping

| Finding field | Nuclei source field |
|---|---|
| `title` | `info.name` |
| `severity` | `info.severity` |
| `cvss.vector` | `info.classification.cvss-metrics` (preferred); fallback canonical vector by severity (see below) |
| `cvss.score` | `info.classification.cvss-score` (preferred); fallback by severity (see below) |
| `cwe` | `info.classification.cwe-id[0]`; fallback: map from template tag/category (see below) |
| `description` | `info.description`; generate from template name and severity if absent |
| `affected_assets` | `[""]` |
| `evidence.steps` | `["Nuclei template  matched at "]` |
| `evidence.tool_output` | raw nuclei JSONL line |
| `impact` | derive from severity and vulnerability type |
| `remediation` | `info.remediation` if present |
| `references` | `info.reference` array |

### CVSS fallback canonical vectors

Use these only when `info.classification.cvss-metrics` is absent or empty. All vectors must pass `^CVSS:3.1/` pattern validation.

| Severity | Fallback vector | Fallback score |
|---|---|---|
| `critical` | `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` | `9.8` |
| `high` | `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` | `8.0` |
| `medium` | `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N` | `5.5` |
| `low` | `CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N` | `3.0` |
| `informational` | `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:N` | `0.0` |

### CWE fallback mapping by template category

Use when `info.classification.cwe-id` is absent:

| Template tag / category | CWE |
|---|---|
| `xss`, `cross-site-scripting` | `CWE-79` |
| `sqli`, `sql-injection` | `CWE-89` |
| `lfi`, `path-traversal` | `CWE-22` |
| `rce`, `code-injection` | `CWE-94` |
| `ssrf` | `CWE-918` |
| `xxe` | `CWE-611` |
| `redirect`, `open-redirect` | `CWE-601` |
| `idor`, `broke

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [thapr0digy](https://github.com/thapr0digy)
- **Source:** [thapr0digy/skills](https://github.com/thapr0digy/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-thapr0digy-skills-scan-vuln
- Seller: https://agentstack.voostack.com/s/thapr0digy
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
