# Recon Active

> Authorized asset discovery — network service enumeration, technology fingerprinting, security configuration analysis, and infrastructure mapping. Invoke via /recon-active [target].

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

## Install

```sh
agentstack add skill-thapr0digy-skills-recon-active
```

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

# /recon-active — Authorized Asset Discovery

You are performing authorized asset discovery and security assessment on approved infrastructure. You conduct network service enumeration and security configuration analysis to build comprehensive asset profiles: service inventory, technology stack, security configurations, and compliance status. Every target must pass scope validation before assessment begins.

---

## Step 1: Resolve Active Engagement

Run the engagement resolver block verbatim before any other logic.

```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
# --- 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 formats

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

```bash
ARG="${1:-}"   # first argument passed to the skill

if [ -n "$ARG" ]; then
  TARGET="$ARG"
elif ls "${OUTPUT_DIR}/recon/passive-"*.json > /dev/null 2>&1; then
  # Fall back to most recent passive recon output — extract discovered hosts
  PASSIVE_FILE=$(ls -t "${OUTPUT_DIR}/recon/passive-"*.json | head -1)
  echo "No target specified — using discovered hosts from $PASSIVE_FILE"
  TARGET="$(jq -r '.sections.resolved_hosts // empty' "$PASSIVE_FILE" 2>/dev/null)"
  # If the passive file has a resolved hosts file reference, use scope.in_scope as fallback
  if [ -z "$TARGET" ]; then
    TARGET=$(jq -r '.scope.in_scope[]' "$ENGAGEMENT_JSON" 2>/dev/null | head -20 | tr '\n' ',')
  fi
else
  # No arg and no passive recon — fall back to scope
  TARGET=$(jq -r '.scope.in_scope[]' "$ENGAGEMENT_JSON" 2>/dev/null | tr '\n' '\n')
  if [ -z "$TARGET" ]; then
    echo "No target provided and no in-scope entries found. Provide a target: /recon-active " >&2
    exit 1
  fi
fi
```

If the argument begins with `@`, treat the remainder as a file path and read targets from it:

```bash
if echo "$ARG" | grep -q '^@'; then
  HOSTS_FILE="${ARG#@}"
  if [ ! -f "$HOSTS_FILE" ]; then
    echo "Hosts file not found: $HOSTS_FILE" >&2
    exit 1
  fi
  TARGET="$HOSTS_FILE"
  TARGET_IS_FILE=true
fi
```

### Determine remote host

```bash
# Check if the engagement has a configured default remote host
DEFAULT_HOST=$(jq -r '.remote_hosts | keys[0] // "local"' "$ENGAGEMENT_JSON" 2>/dev/null)
HOST_NAME="${HOST_NAME:-${DEFAULT_HOST:-local}}"
```

If `$HOST_NAME` resolves to `local` (no remote hosts configured), all commands execute in the current shell.

### Full scope validation

For each target, run complete scope validation per `scope-validator.md`. For active recon, **perform all checks including the testing window check** — active scans generate traffic and must respect testing windows.

```bash
# Normalize target
TARGET_ORIG="$TARGET"

if echo "$TARGET" | grep -qE '^https?://'; then
  TARGET_HOST=$(python3 -c "from urllib.parse import urlparse; print(urlparse('$TARGET').hostname)")
else
  TARGET_HOST="$TARGET"
fi

# Resolve hostname to IPs (skip for CIDRs and bare IPs, and for files)
if [ "${TARGET_IS_FILE:-false}" = "false" ]; then
  if echo "$TARGET_HOST" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(\/[0-9]+)?$'; then
    TARGET_IPS="$TARGET_HOST"
  else
    TARGET_IPS=$(dig +short "$TARGET_HOST" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$')
  fi
fi

# Check out-of-scope (hard block)
OUT_OF_SCOPE=$(jq -r '.scope.out_of_scope[]' "$ENGAGEMENT_JSON" 2>/dev/null)

# Check in-scope (confirm or warn if unlisted)
IN_SCOPE=$(jq -r '.scope.in_scope[]' "$ENGAGEMENT_JSON" 2>/dev/null)

# Check testing window
WINDOWS=$(jq -c '.roe.testing_windows' "$ENGAGEMENT_JSON" 2>/dev/null)
TIMEZONE=$(jq -r '.roe.timezone // "UTC"' "$ENGAGEMENT_JSON" 2>/dev/null)
```

Apply all matching rules from `scope-validator.md`. Log every scope check outcome to `${OUTPUT_DIR}/activity.log` using `action: "scope_check"`.

When processing multiple targets (CIDR or file), validate each target individually before running any scan against it. Do not batch-validate.

Create output directories:

```bash
mkdir -p "${OUTPUT_DIR}/recon"
mkdir -p "${OUTPUT_DIR}/evidence/screenshots"
RECON_DIR="${OUTPUT_DIR}/recon"
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 a tool is missing — skip that step, note the gap in output, and continue. Log every check.

| Tool | Steps | Priority |
|---|---|---|
| `naabu` | 4 — Port Scanning | Required (one of naabu/service discovery) |
| Network Discovery | 5 — Service Fingerprinting | Required (one of naabu/service discovery) |
| `httpx` | 6 — HTTP Probing | Recommended |
| `whatweb` | 7 — Web Technology Detection | Optional |
| `wafw00f` | 8 — WAF Detection | Optional |
| `gowitness` | 9 — Web Screenshots | Recommended — always run when web services found |
| `tlsx` | 10 — SSL/TLS Scanning | Recommended — always run when web services found |

```bash
declare -A TOOL_AVAILABLE

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

If `naabu` and the network service discovery tool are both missing, abort — the skill cannot perform meaningful port scanning without at least one of them.

---

## Step 4: Port Scanning with naabu (Two-Phase)

Skip this step if `naabu` is not available and note the gap. If skipped, proceed directly to Step 5 (nmap will perform its own two-phase port discovery).

Always run both phases automatically:
- **Phase 1 (Quick)** — top 1000 ports, synchronous. Results feed immediately into Steps 5–11 so useful work starts right away.
- **Phase 2 (Full)** — all 65535 ports, asynchronous in tmux. Catches services on non-standard ports (backdoors, debug interfaces, custom apps).

### Phase 1: Quick scan (synchronous)

```bash
NAABU_OUT="${RECON_DIR}/naabu-${ASSESSOR}-$(date +%s).txt"
CMD="naabu -host ${TARGET} -top-ports 1000 -silent -o ${NAABU_OUT}"

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

### Phase 2: Full scan (asynchronous — tmux)

Always launch Phase 2 immediately after Phase 1 completes. This runs in the background while Steps 5–11 proceed using Phase 1 results.

```bash
NAABU_FULL_OUT="${RECON_DIR}/naabu-full-${ASSESSOR}-$(date +%s).txt"
CMD="naabu -host ${TARGET} -p - -silent -o ${NAABU_FULL_OUT}"

SESSION_NAME="${ENGAGEMENT_ID}-naabu-full-$(date +%s)"

# Dispatch per remote-exec.md — SSH, SSM, or local depending on $HOST_NAME
# For local execution:
tmux new-session -d -s "$SESSION_NAME" "$CMD > /dev/null 2>&1"

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

Tell the user:

> Full port scan (Phase 2) dispatched in tmux session `$SESSION_NAME`. Output: `$NAABU_FULL_OUT`
> This will take 10–30+ minutes. Steps 5–11 are proceeding now using Phase 1 (top 1000) results. Check progress with: `tmux attach -t $SESSION_NAME`

Parse the Phase 1 naabu output to extract open ports:

```bash
# Output format: "host:port" per line
OPEN_PORTS=$(awk -F: '{print $2}' "$NAABU_OUT" 2>/dev/null | sort -un | tr '\n' ',' | sed 's/,$//')
```

---

## Step 5: Deep Service Fingerprinting with Network Discovery Tool (Two-Phase when naabu was skipped)

Skip this step if the network service discovery tool is not available and note the gap.

**If naabu ran (Step 4):** Use `$OPEN_PORTS` from naabu Phase 1. The full-port coverage is already handled by naabu Phase 2 running async.

**If naabu was skipped:** the network service discovery tool performs its own two-phase scan:
- **Phase 1** — top 1000 ports, synchronous with `-sV -sC -O` — feeds immediately into Steps 6–11
- **Phase 2** — remaining ports 1001–65535, asynchronous in tmux with `-sV -sC -O`

### Single host — Phase 1 (synchronous)

```bash
SERVICE_DISC_OUT="${RECON_DIR}/service-discovery-${ASSESSOR}-$(date +%s).xml"

if [ -n "${OPEN_PORTS:-}" ]; then
  PORT_FLAG="-p ${OPEN_PORTS}"
  NAABU_PROVIDED_PORTS=true
else
  PORT_FLAG="--top-ports 1000"
  NAABU_PROVIDED_PORTS=false
fi

CMD="nmap -sV -sC -O ${PORT_FLAG} -oX ${SERVICE_DISC_OUT} ${TARGET}"

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

$CMD

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

### Single host — Phase 2 (asynchronous, only when naabu was skipped)

If naabu already launched a full-port scan (Step 4 Phase 2), skip this — naabu covers the remaining ports. Only launch service discovery Phase 2 when the network discovery tool is acting as the sole port scanner.

```bash
if [ "${NAABU_PROVIDED_PORTS:-false}" = "false" ]; then
  SERVICE_DISC_FULL_OUT="${RECON_DIR}/service-discovery-full-${ASSESSOR}-$(date +%s).xml"
  SERVICE_DISC_FULL_SESSION="${ENGAGEMENT_ID}-service-discovery-full-$(date +%s)"
  CMD_FULL="nmap -sV -sC -O -p 1001-65535 -oX ${SERVICE_DISC_FULL_OUT} ${TARGET}"

  tmux new-session -d -s "$SERVICE_DISC_FULL_SESSION" "$CMD_FULL"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "scan" \
    --arg target "$TARGET" \
    --arg command "$CMD_FULL" \
    --arg status "dispatched" \
    --arg session "$SERVICE_DISC_FULL_SESSION" \
    --arg remote_host "$HOST_NAME" \
    --arg phase "full" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target,
      command:$command, status:$status,
      session:$session, remote_host:$remote_host, phase:$phase}' \
    >> "${OUTPUT_DIR}/activity.log"

  echo "Service discovery full-port scan (Phase 2: ports 1001-65535) dispatched in tmux session $SERVICE_DISC_FULL_SESSION"
  echo "Output: $SERVICE_DISC_FULL_OUT — check progress: tmux attach -t $SERVICE_DISC_FULL_SESSION"
fi
```

### IP range or CIDR (asynchronous — tmux)

For ranges with more than one host, dispatch both phases asynchronously per `remote-exec.md`:

```bash
SESSION_NAME="${ENGAGEMENT_ID}-service-discovery-$(date +%s)"
SERVICE_DISC_OUT="${RECON_DIR}/service-discovery-${ASSESSOR}-$(date +%s).xml"
CMD="nmap -sV -sC -O --top-ports 1000 -oX ${SERVICE_DISC_OUT} ${TARGET}"

# Phase 1: top 1000 ports
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 target "$TARGET" \
  --arg command "$CMD" \
  --arg status "dispatched" \
  --arg session "$SESSION_NAME" \
  --arg remote_host "$HOST_NAME" \
  --arg phase "quick" \
  '{ts:$ts, security assessor:$security assessor, action:$action, target:$target,
    command:$command, status:$status,
    session:$session, remote_host:$remote_host, phase:$phase}' \
  >> "${OUTPUT_DIR}/activity.log"

# Phase 2: remaining ports 1001-65535
if [ "${NAABU_PROVIDED_PORTS:-false}" = "false" ]; then
  SESSION_NAME_FULL="${ENGAGEMENT_ID}-service-discovery-full-$(date +%s)"
  SERVICE_DISC_FULL_OUT="${RECON_DIR}/service-discovery-full-${ASSESSOR}-$(date +%s).xml"
  CMD_FULL="nmap -sV -sC -O -p 1001-65535 -oX ${SERVICE_DISC_FULL_OUT} ${TARGET}"

  tmux new-session -d -s "$SESSION_NAME_FULL" "$CMD_FULL"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg security assessor "$ASSESSOR" \
    --arg action "scan" \
    --arg target "$TARGET" \
    --arg command "$CMD_FULL" \
    --arg status "dispatched" \
    --arg session "$SESSION_NAME_FULL" \
    --arg remote_host "$HOST_NAME" \
    --arg phase "full" \
    '{ts:$ts, security assessor:$security assessor, action:$action, target:$target,
      command:$command, status:$status,
      session:$session, remote_host:$remote_host, phase:$phase}' \
    >> "${OUTPUT_DIR}/activity.log"
fi
```

Parse network service discovery XML output to extract service and version data for host profile construction in Step 12.

---

## Step 6: HTTP Probing with httpx

Skip this step if `httpx` is not available. HTTP probing identifies live web services, collects status codes, titles, and technology fingerprints.

Build a hosts file from naabu/service discovery results — include only hosts with open web ports (80, 443, 8080, 8443, 8000, 8888, 9000, 9443, and any other high por

…

## 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-recon-active
- 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%.
