AgentStack
SKILL verified MIT Self-run

Enum Web

skill-thapr0digy-skills-enum-web · by thapr0digy

Web application security assessment — application security testing, authentication validation, configuration analysis, API discovery, technology assessment, and web security compliance evaluation. Invoke via /enum-web [target-url].

No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add skill-thapr0digy-skills-enum-web

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

Are you the author of Enum Web? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

@pentest-core/skills/shared/engagement-resolver.md @pentest-core/skills/shared/scope-validator.md @pentest-core/skills/shared/activity-log.md @pentest-core/skills/shared/safe-auth-validation.md

/enum-web — Web Application Security Assessment

You are conducting comprehensive security assessment of web applications and APIs to evaluate security controls, identify vulnerabilities, and validate compliance with web security standards. Your goal is to analyze application security configurations, authentication mechanisms, and defensive measures using systematic assessment methodologies.


Step 1: Resolve Active Engagement

Run the engagement resolver block verbatim before any other logic.

# --- 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
# --- End Engagement Resolver ---

After this block, $ENGAGEMENT_ID, $OUTPUT_DIR, and $ASSESSOR are guaranteed 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

  • URL — e.g., https://app.acme.com or http://10.0.0.1:8080
  • No argument — fall back to most recent active recon output
ARG="${1:-}"

if [ -n "$ARG" ]; then
  WEB_TARGETS=("$ARG")
elif ls "${OUTPUT_DIR}/recon/active-"*.json > /dev/null 2>&1; then
  RECON_FILE=$(ls -t "${OUTPUT_DIR}/recon/active-"*.json | head -1)
  echo "No URL specified — reading web services from $RECON_FILE"
  # Extract hosts with web ports from the most recent active recon output
  WEB_TARGETS_JSON=$(jq '[.hosts[] | {ip, hostnames, ports: [.ports[] | select(.category == "web")]} | select(.ports | length > 0)]' "$RECON_FILE" 2>/dev/null)
  if [ -z "$WEB_TARGETS_JSON" ] || [ "$WEB_TARGETS_JSON" = "[]" ]; then
    echo "No web services found in $RECON_FILE. Provide a URL: /enum-web " >&2
    exit 1
  fi
  # Build URL list from recon data
  mapfile -t WEB_TARGETS &2
  echo "Provide a URL: /enum-web " >&2
  exit 1
fi

If no recon data is available and no URL was provided, ask the user:

> No active recon data found. Please provide a URL: /enum-web https://app.acme.com

Scope validation

For each URL in WEB_TARGETS, validate against scope before proceeding. Extract the hostname from the URL, resolve to IPs, and check against scope.out_of_scope (hard block) and scope.in_scope (warn if unlisted). Log every check with action: "scope_check".

for WEB_URL in "${WEB_TARGETS[@]}"; do
  TARGET_HOST=$(python3 -c "from urllib.parse import urlparse; print(urlparse('$WEB_URL').hostname)")
  TARGET_IPS=$(dig +short "$TARGET_HOST" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')

  OUT_OF_SCOPE=$(jq -r '.scope.out_of_scope[]' "$ENGAGEMENT_JSON" 2>/dev/null)
  # Match target host and IPs against out_of_scope entries — block on match
  # ... (apply scope-validator.md logic in full) ...

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "scope_check" \
    --arg target "$WEB_URL" \
    --arg status "completed" \
    --arg result "in_scope" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target, status:$status, result:$result}' \
    >> "${OUTPUT_DIR}/activity.log"
done

Target selection

Present the validated target list to the user:

> Web targets identified: > 1. https://app.acme.com > 2. https://api.acme.com > 3. http://10.0.0.5:8080 > > Enumerate which targets? (comma-separated numbers or "all")

Wait for user selection before proceeding.

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

Step 3: Check Tool Availability

Check each tool with which 2>/dev/null. Do not abort if an optional tool is missing — skip that step, note the gap, and continue. Log every check with action: "tool_check".

| Tool | Required | Steps | |---|---|---| | ffuf | Required | 4, 5, 8 — Directory/vhost/param fuzzing | | katana | Required | 6 — Web crawling + JS file discovery | | httpx | Required | 7 — HTTP probing, tech detection, status codes | | jsluice | Optional | 6b — JS endpoint/secret extraction (auto-triggered when JS files found) | | trufflehog | Optional | 6b — Secret scanning in JS files (auto-triggered when JS files found) | | wpscan | Optional | 9 — WordPress enumeration | | arjun | Optional | 8 — Parameter discovery |

All three of ffuf, katana, and httpx are required — each discovers different things (hidden paths, link structure, detailed service info). If any is missing, abort:

declare -A TOOL_AVAILABLE

for TOOL in ffuf katana httpx jsluice trufflehog wpscan arjun; do
  if which "$TOOL" > /dev/null 2>&1; then
    TOOL_AVAILABLE[$TOOL]=true
    STATUS="available"
  else
    TOOL_AVAILABLE[$TOOL]=false
    STATUS="not found"
  fi
  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "tool_check" \
    --arg command "TOOL_AVAILABLE[$TOOL]=false" \
    --arg status "completed" \
    --arg result "$STATUS" \
    '{ts:$ts, security assessor:$security assessor, action:$action, command:$command, status:$status, result:$result}' \
    >> "${OUTPUT_DIR}/activity.log"
done

MISSING_REQUIRED=""
for TOOL in ffuf katana httpx; do
  [ "${TOOL_AVAILABLE[$TOOL]}" = "false" ] && MISSING_REQUIRED="${MISSING_REQUIRED} ${TOOL}"
done

if [ -n "$MISSING_REQUIRED" ]; then
  echo "Required tools missing:${MISSING_REQUIRED}. All three (ffuf, katana, httpx) are needed. Install and retry." >&2
  exit 1
fi

Step 4: Directory and File Discovery with ffuf

Skip this step if ffuf is not available.

Select wordlist — prefer SecLists, fall back to dirb:

WORDLIST="/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt"
[ ! -f "$WORDLIST" ] && WORDLIST="/usr/share/wordlists/dirb/common.txt"

if [ ! -f "$WORDLIST" ]; then
  echo "No wordlist found for directory fuzzing. Install SecLists or dirb." >&2
fi

For each selected target URL, run directory fuzzing. Determine the technology stack from recon data or httpx headers, then run appropriate extension sweeps.

General directory discovery

for WEB_URL in "${SELECTED_TARGETS[@]}"; do
  TARGET_SLUG=$(echo "$WEB_URL" | sed 's|https\?://||;s|[/:]|_|g')
  FFUF_GENERAL="${ENUM_DIR}/ffuf-${TARGET_SLUG}-${ASSESSOR}-$(date +%s).json"
  CMD="ffuf -u ${WEB_URL}/FUZZ -w ${WORDLIST} -mc 200,301,302,403 -o ${FFUF_GENERAL} -of json"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "enum" \
    --arg target "$WEB_URL" \
    --arg command "$CMD" \
    --arg status "dispatched" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target, command:$command, status:$status}' \
    >> "${OUTPUT_DIR}/activity.log"

  $CMD

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

Technology-specific extension sweeps

Every ffuf command MUST include -o -of json. Use separate output files per technology.

PHP:

CMD="ffuf -u ${WEB_URL}/FUZZ -w ${WORDLIST} -e .php,.php.bak -mc 200,301,302,403 -o ${ENUM_DIR}/ffuf-${TARGET_SLUG}-php-${ASSESSOR}-$(date +%s).json -of json"

ASP.NET:

CMD="ffuf -u ${WEB_URL}/FUZZ -w ${WORDLIST} -e .aspx,.ashx,.asmx,.config -mc 200,301,302,403 -o ${ENUM_DIR}/ffuf-${TARGET_SLUG}-aspnet-${ASSESSOR}-$(date +%s).json -of json"

Java:

CMD="ffuf -u ${WEB_URL}/FUZZ -w ${WORDLIST} -e .jsp,.do,.action -mc 200,301,302,403 -o ${ENUM_DIR}/ffuf-${TARGET_SLUG}-java-${ASSESSOR}-$(date +%s).json -of json"

Node.js:

CMD="ffuf -u ${WEB_URL}/FUZZ -w ${WORDLIST} -e .js,.json -mc 200,301,302,403 -o ${ENUM_DIR}/ffuf-${TARGET_SLUG}-node-${ASSESSOR}-$(date +%s).json -of json"

For large wordlists (>100k lines), dispatch each ffuf command asynchronously in tmux and log status: "dispatched". For small wordlists, run synchronously. Dispatch rule:

WORDLIST_LINES=$(wc -l /dev/null)

Step 5: Virtual Host Discovery with ffuf

Skip this step if ffuf is not available.

Virtual host (vhost) fuzzing enumerates subdomains served from the same IP via Host header manipulation.

VHOST_WORDLIST="/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt"
[ ! -f "$VHOST_WORDLIST" ] && VHOST_WORDLIST="/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt"

if [ ! -f "$VHOST_WORDLIST" ]; then
  echo "Skipping vhost discovery — no vhost wordlist found." >&2
fi

Run Host header fuzzing for each target. Filter by response size to eliminate wildcard responses — first probe the base URL to get its size, then exclude that size in ffuf:

for WEB_URL in "${SELECTED_TARGETS[@]}"; do
  TARGET_SLUG=$(echo "$WEB_URL" | sed 's|https\?://||;s|[/:]|_|g')
  TARGET_HOST=$(python3 -c "from urllib.parse import urlparse; print(urlparse('$WEB_URL').hostname)")
  TARGET_DOMAIN=$(echo "$TARGET_HOST" | sed 's/^[^.]*\.//')

  # Probe baseline response size
  BASELINE_SIZE=$(curl -s -o /dev/null -w "%{size_download}" "$WEB_URL")

  VHOST_OUT="${ENUM_DIR}/ffuf-vhost-${TARGET_SLUG}-${ASSESSOR}-$(date +%s).json"
  CMD="ffuf -u ${WEB_URL} -H 'Host: FUZZ.${TARGET_DOMAIN}' -w ${VHOST_WORDLIST} -fs ${BASELINE_SIZE} -mc 200,301,302,403 -o ${VHOST_OUT} -of json"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "enum" \
    --arg target "$WEB_URL" \
    --arg command "$CMD" \
    --arg status "dispatched" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target, command:$command, status:$status}' \
    >> "${OUTPUT_DIR}/activity.log"

  SESSION_NAME="${ENGAGEMENT_ID}-ffuf-vhost-${TARGET_SLUG}-$(date +%s)"
  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 "enum" \
    --arg target "$WEB_URL" \
    --arg command "echo \"Vhost fuzzing dispatched in tmux session: $SESSION_NAME\"" \
    --arg status "dispatched" \
    --arg session "$SESSION_NAME" \
    --arg output_file "$VHOST_OUT" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target, command:$command, status:$status, session:$session, output_file:$output_file}' \
    >> "${OUTPUT_DIR}/activity.log"

  echo "Vhost fuzzing dispatched in tmux session: $SESSION_NAME"
  echo "Output: $VHOST_OUT"
done

Parse vhost results when complete:

DISCOVERED_VHOSTS=$(jq '[.results[].input.FUZZ + "."]' "$VHOST_OUT" 2>/dev/null)

Step 6: Web Crawling with katana

Skip this step if katana is not available.

Crawl each target to discover endpoints, forms, JS files, and API routes.

for WEB_URL in "${SELECTED_TARGETS[@]}"; do
  TARGET_SLUG=$(echo "$WEB_URL" | sed 's|https\?://||;s|[/:]|_|g')
  KATANA_OUT="${ENUM_DIR}/katana-${TARGET_SLUG}-${ASSESSOR}-$(date +%s).json"
  CMD="katana -u ${WEB_URL} -d 3 -jc -kf -ef css,png,jpg,gif,svg,woff -json -o ${KATANA_OUT}"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "enum" \
    --arg target "$WEB_URL" \
    --arg command "$CMD" \
    --arg status "dispatched" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target, command:$command, status:$status}' \
    >> "${OUTPUT_DIR}/activity.log"

  $CMD

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

Parse the katana output to extract:

# All crawled URLs with method and source
CRAWLED_URLS=$(jq '[.[] | {url: .request.url, method: .request.method, source: .response.technologies // "katana"}]' "$KATANA_OUT" 2>/dev/null)

# JS files for route extraction in Step 7
JS_FILES=$(jq -r '[.[] | select(.request.url | endswith(".js")) | .request.url] | unique[]' "$KATANA_OUT" 2>/dev/null)

# Forms — potential auth/input endpoints
FORMS=$(jq '[.[] | select(.request.method == "POST") | {url: .request.url, method: "POST"}]' "$KATANA_OUT" 2>/dev/null)

Download JS files and search for embedded routes, API endpoints, and sensitive strings:

for JS_URL in $JS_FILES; do
  JS_SLUG=$(echo "$JS_URL" | md5sum | awk '{print $1}')
  JS_LOCAL="${ENUM_DIR}/js-${JS_SLUG}.js"
  curl -sk "$JS_URL" -o "$JS_LOCAL"
  # Extract path-like strings that could be API routes
  grep -oE '"(/[a-zA-Z0-9_/-]+)"' "$JS_LOCAL" | sort -u
done

Step 6b: JavaScript Analysis (auto-triggered)

This step runs automatically whenever JS files are discovered in Step 6. JS files are a high-value target for endpoints, secrets, API keys, and internal URLs. Do not skip or defer this step — run it inline before proceeding to Step 7.

If $JS_FILES from Step 6 is non-empty, proceed. If no JS files were found, skip with a note.

Collect and download JS files

JS_DIR="${ENUM_DIR}/js-files-${ASSESSOR}-$(date +%s)"
mkdir -p "$JS_DIR"

JS_URL_LIST="${ENUM_DIR}/js-urls-${TARGET_SLUG}.txt"
echo "$JS_FILES" > "$JS_URL_LIST"
JS_COUNT=$(wc -l  "$JSLUICE_URLS"
  find "$JS_DIR" -name "*.js" -exec jsluice secrets {} \; | jq -s '.' > "$JSLUICE_SECRETS"

  JSLUICE_URL_COUNT=$(jq 'length' "$JSLUICE_URLS" 2>/dev/null || echo 0)
  JSLUICE_SECRET_COUNT=$(jq 'length' "$JSLUICE_SECRETS" 2>/dev/null || echo 0)

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "enum" \
    --arg target "$WEB_URL" \
    --arg command "find $JS_DIR -name '*.js' -exec jsluice urls/secrets {} \; | jq -s '.'" \
    --arg status "completed" \
    --arg result "endpoints: ${JSLUICE_URL_COUNT}, secrets: ${JSLUICE_SECRET_COUNT}" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target, command:$command, status:$status, result:$result}' \
    >> "${OUTPUT_DIR}/activity.log"
else
  echo "jsluice not available — endpoint/secret extraction skipped. Install: go install github.com/BishopFox/jsluice/cmd/jsluice@latest"
fi

Parse jsluice output for: API endpoints, internal URLs, path patterns. Merge with the endpoint list being built for Step 7.

trufflehog — secret scanning in JS files

if [ "${TOOL_AVAILABLE[trufflehog]:-false}" = "true" ]; then
  TRUFFLEHOG_OUT="${ENUM_DIR}/trufflehog-js-${ASSESSOR}-$(date +%s).json"
  truffle

…

## 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.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.