Install
$ agentstack add skill-thapr0digy-skills-scan-vuln ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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.
# --- 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
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).
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.
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):
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:
TEMPLATE_ARGS="-t cves/ -t misconfiguration/ -t exposures/"
Tech-stack-aware templates
Read httpx and whatweb outputs to determine which technologies are present:
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/ |
# 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:
if [ -n "$CUSTOM_TEMPLATES_DIR" ] && [ -d "$CUSTOM_TEMPLATES_DIR" ]; then
TEMPLATE_ARGS="$TEMPLATE_ARGS -t ${CUSTOM_TEMPLATES_DIR}"
fi
Severity filter
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:
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):
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:
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
NUCLEI_RAW="${OUTPUT_DIR}/findings/nuclei-raw-${ASSESSOR}-$(date +%s).jsonl"
Execution command
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
TARGET_COUNT=$(wc -l > "${OUTPUT_DIR}/activity.log"
Fewer than 10 targets — run synchronously:
$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:
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
- Source: thapr0digy/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.