AgentStack
SKILL verified MIT Self-run

Recon Passive

skill-thapr0digy-skills-recon-passive · by thapr0digy

Open source intelligence gathering — collect publicly available information, discover digital footprint, and analyze organizational assets using authorized OSINT methods. Invoke via /recon-passive [domain].

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

Install

$ agentstack add skill-thapr0digy-skills-recon-passive

✓ 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 Recon Passive? 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

/recon-passive — Open Source Intelligence Gathering

You are conducting authorized open source intelligence (OSINT) collection on organizational assets. Your goal is to gather publicly available intelligence using legitimate research methods WITHOUT directly interacting with target infrastructure. All data collection uses public databases, search engines, and archived records within authorized scope.


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

If an argument was provided (e.g., /recon-passive acme.com)

Use the argument as the primary target domain:

TARGET_DOMAIN="acme.com"   # from user argument

If no argument was provided

Extract domains from the engagement scope:

SCOPE_DOMAINS=$(jq -r '.scope.in_scope[] | select(.type == "domain") | .value' "$ENGAGEMENT_JSON" \
  | sed 's/^\*\.//')

If SCOPE_DOMAINS is empty after extraction, ask the user which domain(s) to run passive recon against before continuing.

Build a list of all targets to enumerate — there may be more than one. For each target in the list, run all subsequent steps.

Scope validation

For each target domain, run scope validation per the scope validator instructions. For passive recon, skip the testing window check — passive recon produces no traffic to the target and is permitted outside testing windows. Still perform the out-of-scope hard block and the in-scope/unlisted confirmation checks.

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

Log all scope check outcomes to ${OUTPUT_DIR}/activity.log using action: "scope_check" per the scope validator log entry reference.


Step 3: Check Tool Availability

Check for each tool with which 2>/dev/null. Record availability — do not abort if a tool is missing; skip that step and note the gap in the output.

Required tools and the steps they support:

| Tool | Step | |---|---| | subfinder | 5 — Subdomain Enumeration | | amass | 5, 14 — Subdomain Enumeration, ASN | | dnsx | 7 — DNS Resolution | | theHarvester | 8 — Email and Name Harvesting | | gau | 12 — URL Discovery | | waybackurls | 12 — URL Discovery | | cloud_enum | 13 — Cloud Asset Discovery | | cloudlist | 13 — Cloud Asset Discovery | | trufflehog | 10 — GitHub/GitLab Recon |

for TOOL in subfinder amass dnsx theHarvester gau waybackurls cloud_enum cloudlist trufflehog; do
  if which "$TOOL" > /dev/null 2>&1; then
    jq -nc \
      --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
      --arg assessor "$ASSESSOR" \
      --arg action "tool_check" \
      --arg command "which $TOOL" \
      --arg status "completed" \
      --arg result "available" \
      '{ts:$ts, assessor:$assessor, action:$action, command:$command, status:$status, result:$result}' \
      >> "${OUTPUT_DIR}/activity.log"
  else
    jq -nc \
      --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
      --arg assessor "$ASSESSOR" \
      --arg action "tool_check" \
      --arg command "which $TOOL" \
      --arg status "skipped" \
      --arg result "not found" \
      '{ts:$ts, assessor:$assessor, action:$action, command:$command, status:$status, result:$result}' \
      >> "${OUTPUT_DIR}/activity.log"
  fi
done

Create the output directory for this recon run:

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

Step 4: WHOIS and DNS Records

Run synchronously. These tools are always available on standard systems.

# WHOIS
whois "$TARGET_DOMAIN" > "${RECON_DIR}/whois-${TARGET_DOMAIN}.txt" 2>/dev/null

# DNS records
for QTYPE in ANY MX TXT NS SOA; do
  dig "$QTYPE" "$TARGET_DOMAIN" +noall +answer >> "${RECON_DIR}/dns-${TARGET_DOMAIN}.txt" 2>/dev/null
done

Parse and extract from the results:

  • Registrant — organization name and contact email from WHOIS output
  • Nameservers — authoritative NS records
  • MX records — mail exchangers and their priorities
  • SPFv=spf1 entry from TXT records
  • DKIM — any ._domainkey. TXT entries discovered
  • DMARC_dmarc. TXT record

Store parsed values in a WHOIS_DATA and DNS_DATA structure for inclusion in the final JSON output (Step 16).

Log the action:

jq -nc \
  --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
  --arg assessor "$ASSESSOR" \
  --arg action "enum" \
  --arg target "$TARGET_DOMAIN" \
  --arg command "whois $TARGET_DOMAIN; dig ANY/MX/TXT/NS/SOA $TARGET_DOMAIN" \
  --arg status "completed" \
  --arg output_file "${RECON_DIR}/dns-${TARGET_DOMAIN}.txt" \
  '{ts:$ts, assessor:$assessor, action:$action, target:$target,
    command:$command, status:$status, output_file:$output_file}' \
  >> "${OUTPUT_DIR}/activity.log"

Step 5: Subdomain Enumeration

Run only for root domains. If the target is already a specific subdomain (e.g., app.acme.com — has more than one dot-separated label before the TLD), skip this step entirely and note: "Target is a subdomain — subdomain enumeration skipped." The target is already resolved.

Detect root vs subdomain:

# Count labels before TLD. "acme.com" = root, "app.acme.com" = subdomain.
LABEL_COUNT=$(echo "$TARGET_DOMAIN" | awk -F. '{print NF}')
IS_ROOT_DOMAIN=false
if [ "$LABEL_COUNT" -le 2 ]; then
  IS_ROOT_DOMAIN=true
elif echo "$TARGET_DOMAIN" | grep -qE '\.(co|com|org|net|gov)\.[a-z]{2}$'; then
  # Handle ccTLDs like .co.uk, .com.au — 3 labels is still a root domain
  [ "$LABEL_COUNT" -le 3 ] && IS_ROOT_DOMAIN=true
fi

If $IS_ROOT_DOMAIN is false, skip to Step 6.

For root domains, run subfinder and amass in passive mode. For large domains (more than 500 subdomains expected, or when the user requests it), offer to run these asynchronously.

SUBDOMAINS_FILE="${RECON_DIR}/subdomains-${TARGET_DOMAIN}.txt"

# subfinder
if [ "$IS_ROOT_DOMAIN" = "true" ] && which subfinder > /dev/null 2>&1; then
  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "subfinder -d \"$TARGET_DOMAIN\" -silent -o \"${RECON_DIR}/subfinder-${TARGET_DOMAIN}.txt\"" --arg status "dispatched" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target, command:$command, status:$status}' \
    >> "${OUTPUT_DIR}/activity.log"

  subfinder -d "$TARGET_DOMAIN" -silent -o "${RECON_DIR}/subfinder-${TARGET_DOMAIN}.txt"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "subfinder -d \"$TARGET_DOMAIN\" -silent -o \"${RECON_DIR}/subfinder-${TARGET_DOMAIN}.txt\"" \
    --arg status "$([ $? -eq 0 ] && echo completed || echo failed)" \
    --arg output_file "${RECON_DIR}/subfinder-${TARGET_DOMAIN}.txt" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target,
      command:$command, status:$status, output_file:$output_file}' \
    >> "${OUTPUT_DIR}/activity.log"
fi

# amass (passive only)
if [ "$IS_ROOT_DOMAIN" = "true" ] && which amass > /dev/null 2>&1; then
  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "amass enum -passive -d \"$TARGET_DOMAIN\" -o \"${RECON_DIR}/amass-${TARGET_DOMAIN}.txt\"" --arg status "dispatched" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target, command:$command, status:$status}' \
    >> "${OUTPUT_DIR}/activity.log"

  amass enum -passive -d "$TARGET_DOMAIN" -o "${RECON_DIR}/amass-${TARGET_DOMAIN}.txt"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "amass enum -passive -d \"$TARGET_DOMAIN\" -o \"${RECON_DIR}/amass-${TARGET_DOMAIN}.txt\"" \
    --arg status "$([ $? -eq 0 ] && echo completed || echo failed)" \
    --arg output_file "${RECON_DIR}/amass-${TARGET_DOMAIN}.txt" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target,
      command:$command, status:$status, output_file:$output_file}' \
    >> "${OUTPUT_DIR}/activity.log"
fi

# Merge and deduplicate
cat "${RECON_DIR}/subfinder-${TARGET_DOMAIN}.txt" \
    "${RECON_DIR}/amass-${TARGET_DOMAIN}.txt" \
    2>/dev/null \
  | sort -u > "$SUBDOMAINS_FILE"

Step 6: Certificate Transparency

Query crt.sh database directly for certificate transparency logs — faster than web API.

CT_FILE="${RECON_DIR}/crt-${TARGET_DOMAIN}.txt"

# Direct PostgreSQL query to crt.sh database for better performance
if which psql > /dev/null 2>&1; then
  # Use timeout to avoid hanging and optimize query for performance
  timeout 30s psql -h crt.sh -p 5432 -U guest -d certwatch -t -c "
    SELECT DISTINCT regexp_replace(name_value, '^\*\.', '', 'g') 
    FROM certificate_identity 
    WHERE lower(name_value) LIKE lower('%.${TARGET_DOMAIN}') 
       OR lower(name_value) = lower('${TARGET_DOMAIN}')
    LIMIT 5000;
  " 2>/dev/null | grep -v '^\s*$' | sort -u > "$CT_FILE"
  
  # Check if query succeeded and produced results
  if [ $? -ne 0 ] || [ ! -s "$CT_FILE" ]; then
    echo "Warning: PostgreSQL query failed or timed out, falling back to web API"
    curl -s "https://crt.sh/?q=%25.${TARGET_DOMAIN}&output=json" \
      | jq -r '.[].name_value' \
      | sed 's/\*\.//g' \
      | sort -u > "$CT_FILE"
  fi
else
  # Fallback to web API if psql not available
  echo "Info: psql not found, using web API"
  curl -s "https://crt.sh/?q=%25.${TARGET_DOMAIN}&output=json" \
    | jq -r '.[].name_value' \
    | sed 's/\*\.//g' \
    | sort -u > "$CT_FILE"
fi

Merge the crt.sh results into the deduplicated subdomain list:

cat "$SUBDOMAINS_FILE" "$CT_FILE" | sort -u > "${RECON_DIR}/subdomains-merged-${TARGET_DOMAIN}.txt"
mv "${RECON_DIR}/subdomains-merged-${TARGET_DOMAIN}.txt" "$SUBDOMAINS_FILE"

Log the action:

# Determine which method was used based on file size and psql availability
if which psql > /dev/null 2>&1 && [ -s "$CT_FILE" ]; then
  COMMAND_DESC="PostgreSQL direct query to crt.sh certificate transparency database"
  METHOD="database"
else
  COMMAND_DESC="Web API fallback query to crt.sh certificate transparency logs"
  METHOD="web_api"
fi

jq -nc \
  --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
  --arg assessor "$ASSESSOR" \
  --arg action "enum" \
  --arg target "$TARGET_DOMAIN" \
  --arg command "$COMMAND_DESC" \
  --arg method "$METHOD" \
  --arg status "completed" \
  --arg output_file "$CT_FILE" \
  '{ts:$ts, assessor:$assessor, action:$action, target:$target,
    command:$command, method:$method, status:$status, output_file:$output_file}' \
  >> "${OUTPUT_DIR}/activity.log"

Step 7: DNS Resolution

Resolve each discovered subdomain to its A record(s). Skip if dnsx is not available and note the gap.

RESOLVED_FILE="${RECON_DIR}/resolved-${TARGET_DOMAIN}.json"

if which dnsx > /dev/null 2>&1; then
  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "dnsx -l \"$SUBDOMAINS_FILE\" -silent -a -resp -json -o \"$RESOLVED_FILE\"" --arg status "dispatched" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target, command:$command, status:$status}' \
    >> "${OUTPUT_DIR}/activity.log"

  dnsx -l "$SUBDOMAINS_FILE" -silent -a -resp -json -o "$RESOLVED_FILE"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "dnsx -l \"$SUBDOMAINS_FILE\" -silent -a -resp -json -o \"$RESOLVED_FILE\"" \
    --arg status "$([ $? -eq 0 ] && echo completed || echo failed)" \
    --arg output_file "$RESOLVED_FILE" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target,
      command:$command, status:$status, output_file:$output_file}' \
    >> "${OUTPUT_DIR}/activity.log"
else
  echo "dnsx not available — DNS resolution step skipped" >&2
fi

Step 8: Email and Name Harvesting

Use theHarvester to collect email addresses, employee names, and associated metadata from public sources.

HARVESTER_DIR="${RECON_DIR}/harvester-${TARGET_DOMAIN}"
mkdir -p "$HARVESTER_DIR"

if which theHarvester > /dev/null 2>&1; then
  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "theHarvester -d \"$TARGET_DOMAIN\" -b all -f \"${HARVESTER_DIR}/output\"" --arg status "dispatched" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target, command:$command, status:$status}' \
    >> "${OUTPUT_DIR}/activity.log"

  theHarvester -d "$TARGET_DOMAIN" -b all -f "${HARVESTER_DIR}/output"

  jq -nc \
    --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    --arg assessor "$ASSESSOR" --arg action "enum" \
    --arg target "$TARGET_DOMAIN" --arg command "theHarvester -d \"$TARGET_DOMAIN\" -b all -f \"${HARVESTER_DIR}/output\"" \
    --arg status "$([ $? -eq 0 ] && echo completed || echo failed)" \
    --arg output_file "${HARVESTER_DIR}/output.json" \
    '{ts:$ts, assessor:$assessor, action:$action, target:$target,
      command:$command, status:$status, output_file:$output_file}' \
    >> "${OUTPUT_DIR}/activity.log"
else
  echo "theHarvester not available — email harvesting step skipped" >&2
fi

Step 9: Google Dorking

Generate dork queries for the target. Do NOT execute these queries — present them to the user for manual execution in a browser. Automated Google scraping violates ToS and often results in CAPTCHA blocks.

Present the following queries to the user as a formatted list:

site:$TARGET_DOMAIN                                    # all indexed pages
site:$TARGET_DOMAIN filetype:pdf                       # public PDF documents
site:$TARGET_DOMAIN filetype:xls OR filetype:xlsx      # spreadsheets
site:$TARGET_DOMAIN filetype:doc OR filetype:docx      # Word documents
site:$TARGET_DOMAIN inurl:admin                        # admin panels
site:$TARGET_DOMAIN inurl:login                        # login pages
site:$TARGET_DOMAIN inurl:api                          # API endpoints
site:$TARGET_DOMAIN intitle:"index of"                 # directory listings
site:$TARGET_DOMAIN ext:env OR ext:config              # config files
site:$TARGET_DOMAIN ext:sql OR ext:bak OR ext:backup   # database/backup files
"@$TARGET_DOMAIN" -site:$TARGET_DOMAIN                 # email addresses off-site
"$TARGET_DOMAIN" password OR credential OR secret      # credential leaks

Tell the user: "These dork queries are pr

Source & license

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

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.