# Wslc Pentest Workflow

> Run authorized pentest workflows inside persistent WSL containers and orchestrate with imported pentest skills (methodology, tool-selection, and tool-specific skills).

- Skill: `timsonner/wslc-pentest-workflow` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add timsonner/wslc-pentest-workflow`
- Raw SKILL.md: https://api.skillmd.com/api/skills/timsonner/wslc-pentest-workflow/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- License: MIT
- Author: timsonner (https://skillmd.com/u/timsonner)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/timsonner/wslc-pentest-workflow

---


# WSLC Pentest Workflow

Use this skill when pentesting from Windows while isolating tooling in WSL Containers (`wslc`) and reusing imported pentest skills.

## Safety + scope guardrails

1. Authorized assessments only (written permission, defined scope).
2. Never run active testing outside approved targets.
3. Default to non-destructive validation first.
4. Keep logs and timestamps for all commands.

## Pair this skill with

- `pentest-methodology` (phase workflow)
- `tool-selection` (pick lowest-risk effective tools)
- Tool-specific skills as needed (`nmap`, `ffuf`, `sqlmap`, `burp-suite`, etc.)

Load sequence recommendation:
1. `skill_view(name="wslc-pentest-workflow")`
2. `skill_view(name="pentest-methodology")`
3. `skill_view(name="tool-selection")`
4. `skill_view(name="<tool-skill>")` per task

## Preflight checks (Windows host)

Run before creating/using containers:

```bash
command -v wsl && wsl --version
command -v wslc && wslc version
wslc images
wslc container ps -a
```

If `wslc` is not found in PATH, use:

```bash
'/c/Program Files/WSL/wslc.exe' version
```

## Standard container profile (persistent Kali)

Use one persistent container per engagement, and always increment the engagement ID.

```bash
# Compute next numeric engagement id from existing workspaces (001, 002, ...)
BASE="/c/Users/$USER/pentest-workspace"
mkdir -p "$BASE"
LAST_ID=$(find "$BASE" -maxdepth 1 -type d -name 'engagement-*' 2>/dev/null \
  | sed -E 's#.*/engagement-([0-9]{3})$#\1#' \
  | sort -n | tail -1)
[ -z "$LAST_ID" ] && LAST_ID=0
ENG_ID=$(printf '%03d' $((10#$LAST_ID + 1)))

ENG_DIR="engagement-$ENG_ID"
CONT_NAME="kali-pentest-$ENG_ID"

# Host workspace
mkdir -p "$BASE/$ENG_DIR"

# Create long-lived container with mounted workspace
wslc run -d --name "$CONT_NAME" \
  -v "C:/Users/$USER/pentest-workspace/$ENG_DIR:/workspace" \
  kalilinux/kali-rolling sleep infinity

# Verify
wslc container ps -a
echo "Using ENG_ID=$ENG_ID, CONT_NAME=$CONT_NAME"
```

Naming rule: never reuse `001` across engagements. Always create a fresh incremented ID.

Bootstrap base tooling once (run after container creation):

```bash
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'apt-get update -qq && apt-get install -y -qq \
  nmap dnsutils curl wget netcat-traditional gobuster ffuf nikto sqlmap \
  enum4linux-ng smbclient ldap-utils python3 python3-pip python3-pexpect git jq \
  john hydra sshpass wordlists dirb expect 2>&1 | tail -5'
```

Unpack rockyou after installing wordlists:

```bash
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'gunzip -k /usr/share/wordlists/rockyou.txt.gz 2>/dev/null; \
   ls -lh /usr/share/wordlists/rockyou.txt'
```

Notes:
- `wordlists` package installs rockyou.txt.gz — must be gunzipped before use.
- `dirb` package is required for `/usr/share/wordlists/dirb/common.txt` — the `wordlists` package alone does NOT create this path. Always include `dirb` in the bootstrap install.
- `python3-pexpect` enables SSH automation scripts (key passphrase, sudo interaction) — required for `pexpect`-based exploit scripts. Install with `apt-get install python3-pexpect`, not `pip` (externally-managed-environment error).
- `expect` (the shell tool) is also useful as a fallback but `python3-pexpect` is more reliable for complex multi-prompt SSH sessions.
- `john` ships with best64.rule pre-configured in /etc/john/john.conf; reference it as `--rules=best64`.
- Install `hashcat` separately only if GPU/OpenCL is available (see Pitfalls).

## Execution pattern (Hermes + skills)

1. Keep planning on host (Hermes).
2. Execute scanner/tool commands in `kali-pentest-<ENG_ID>` via `wslc exec ...`.
3. Save outputs to `/workspace/artifacts/...` so evidence persists on Windows host.
4. Map each action to pentest-methodology phases.

Examples:

```bash
# Recon
wslc exec kali-pentest-<ENG_ID> bash -lc 'nmap -sV -sC -oN /workspace/artifacts/nmap-initial.txt <TARGET>'

# Web content discovery
wslc exec kali-pentest-<ENG_ID> bash -lc 'ffuf -u http://<TARGET>/FUZZ -w /usr/share/wordlists/dirb/common.txt -o /workspace/artifacts/ffuf.json -of json'

# Basic web triage
wslc exec kali-pentest-<ENG_ID> bash -lc 'nikto -h http://<TARGET> -output /workspace/artifacts/nikto.txt'
```

## File + evidence conventions

Recommended host tree:

```text
C:\Users\<user>\pentest-workspace\engagement-<ENG_ID>\
  scope.md
  logbook.md
  artifacts\
  reports\
```

Command logging convention (append to `logbook.md`):
- timestamp
- command
- objective
- result summary
- artifact path

## Useful lifecycle commands

```bash
# Logs and stats
wslc logs kali-pentest-<ENG_ID>
wslc stats

# Shell into container
wslc exec -it kali-pentest-<ENG_ID> bash

# Stop/start between sessions
wslc stop kali-pentest-<ENG_ID>
wslc start kali-pentest-<ENG_ID>

# Remove when engagement ends
wslc remove kali-pentest-<ENG_ID>
```

## When to use privileged workaround

Only if a required test needs capabilities unavailable in default `wslc run` flags (e.g., network admin operations):

```bash
wslc system session run docker run -d --name privileged-kali --privileged kalilinux/kali-rolling sleep infinity
```

Use this sparingly, document why, and tear down after use.

## Adapting pentest-methodology to wslc

The `pentest-methodology` skill uses `kali-exec` (MCP server pattern). When using wslc instead, substitute every `kali-exec "cmd"` with:

```bash
wslc exec kali-pentest-<ENG_ID> bash -lc 'cmd'
```

The logic, phase ordering, and reporting structure from `pentest-methodology` remain the same.

## Interception Proxy Routing inside WSL Containers (Caido / Burp Pattern)

To capture and analyze web/API security testing traffic generated by command-line tools (such as curl, ffuf, nikto, or nuclei) or headless browsers, you can route container traffic through an interception proxy. Learning from the **Strix** design pattern, there are two highly effective approaches:

### Approach 1: Strix-Inspired In-Container Caido Sidecar (Recommended)
Running Caido inside the WSL container itself bypasses all Windows Firewall and WSL virtual routing boundaries. Since Caido runs on loopback `127.0.0.1:8080` locally inside the container, traffic routing is 100% reliable and zero-friction. 

1. **Expose the Proxy Port on Startup:**
   Map port `8080` (or `48080`) when starting the container so you can view the Caido Web UI from your Windows browser:
   ```bash
   wslc run -d --name "$CONT_NAME" \
     -p 127.0.0.1:8080:8080 \
     -v "C:/Users/$USER/pentest-workspace/$ENG_DIR:/workspace" \
     kalilinux/kali-rolling sleep infinity
   ```

2. **Download and Install Caido CLI in the Container:**
   ```bash
   wslc exec kali-pentest-<ENG_ID> bash -lc \
     'wget -O /tmp/caido.tar.gz "https://caido.download/releases/v0.38.0/caido-cli-v0.38.0-linux-x86_64.tar.gz" && \
      tar -xzf /tmp/caido.tar.gz -C /usr/local/bin/ caido-cli && \
      chmod +x /usr/local/bin/caido-cli && \
      rm /tmp/caido.tar.gz'
   ```

3. **Launch Caido CLI in the Background:**
   ```bash
   wslc exec kali-pentest-<ENG_ID> bash -lc \
     'caido-cli --listen 0.0.0.0:8080 --allow-guests --no-open --no-logging &'
   ```

4. **Automate System-Wide Proxy & CA Certificate Trust:**
   Wait for Caido to boot, fetch its CA certificate via curl, install it into the system-wide CA trust store, and write proxy exports to `/etc/profile.d/proxy.sh` so every shell automatically proxies all traffic:
   ```bash
   wslc exec kali-pentest-<ENG_ID> bash -lc \
     'sleep 3 && \
      curl -fsSL http://127.0.0.1:8080/ca.crt -o /tmp/ca.crt && \
      cp /tmp/ca.crt /usr/local/share/ca-certificates/ca-caido.crt && \
      update-ca-certificates && \
      cat << "EOF" | tee /etc/profile.d/proxy.sh
   export http_proxy="http://127.0.0.1:8080"
   export https_proxy="http://127.0.0.1:8080"
   export HTTP_PROXY="http://127.0.0.1:8080"
   export HTTPS_PROXY="http://127.0.0.1:8080"
   export ALL_PROXY="http://127.0.0.1:8080"
   export NO_PROXY="localhost,127.0.0.1"
   export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt"
   export SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt"
   EOF
   '
   ```
   Now, any tool in the container (such as `curl`, `ffuf`, or `nuclei`) will seamlessly route through Caido and automatically verify its SSL certificate without needing insecure flags like `-k`!
   
   *Tip:* Simply open `http://127.0.0.1:8080` in your host web browser (Windows) to interactively inspect, filter, and replay testing traffic!

### Approach 2: Host Proxy Setup (Traditional)
Route container traffic through an interception proxy (Caido/Burp) running directly on the Windows host:

1. **Host Proxy Setup:**
   Ensure your host proxy binds to `0.0.0.0` or the specific WSL virtual interface (e.g., port 8080).
   Find your Windows host IP address from within the container:
   ```bash
   HOST_IP=$(ip route | grep default | awk "{print \$3}")
   ```

2. **Environment Proxy Exports:**
   Before running utilities, export the proxy environment variables pointing to the Windows host IP:
   ```bash
   export http_proxy="http://$HOST_IP:8080"
   export https_proxy="http://$HOST_IP:8080"
   export REQUESTS_CA_BUNDLE="/workspace/ca-certificates/cacert.pem" # If SSL inspection is active
   ```

3. **Execution example:**
   ```bash
   wslc exec kali-pentest-<ENG_ID> bash -lc \
     'curl -k -i -s http://<TARGET>/api/users'
   ```

## Hash cracking in-container (john vs hashcat)

- `john` works out of the box on CPU inside wslc containers. Use it.
- `hashcat` requires OpenCL/GPU — not available in standard wslc containers. Attempting it fails silently with `CL_PLATFORM_NOT_FOUND_KHR`. Do not install hashcat inside the container unless you have GPU passthrough.
- For best64-rule cracking with john:
  ```bash
  john --format=Raw-SHA512 --wordlist=/usr/share/wordlists/rockyou.txt \
    --rules=best64 --fork=4 /tmp/target.hash
  john --show --format=Raw-SHA512 /tmp/target.hash
  ```
  (`best64` is pre-registered in /etc/john/john.conf — no path needed.)

## Heredoc quoting hazard with wslc exec

Passing heredocs or complex scripts through `wslc exec kali-... bash -lc '...'` causes quoting conflicts. Instead, write scripts to a file and copy them:

```bash
# On Windows/host:
cat > /c/Users/admin/pentest-workspace/engagement-<ENG_ID>/artifacts/myscript.sh << 'EOF'
#!/bin/bash
# your script here
EOF

# Copy to container then run:
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'sshpass -p "PASS" scp -o StrictHostKeyChecking=no \
   /workspace/artifacts/myscript.sh user@TARGET:/tmp/myscript.sh && \
   sshpass -p "PASS" ssh -o StrictHostKeyChecking=no user@TARGET "bash /tmp/myscript.sh"'
```


IMPORTANT: Do NOT modify `/etc/hosts` inside the container via `wslc exec` — this is blocked
by the Hermes safety system as an irreversible action. Use explicit `Host:` headers for ALL
vhost-aware requests instead. Tools that support `-H` or `--host` flags work without any
hosts-file change.

Always check HTML source comments for vhost hints:
```bash
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'curl -s http://<TARGET>/ | grep -i "TODO\|vhost\|host\|domain\|thm\|htb"'
```

Then probe with `Host:` header before running gobuster:
```bash
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'curl -s -H "Host: discovered-vhost.thm" http://<TARGET>/ | head -40'
```

Before running ffuf vhost fuzzing, get the baseline response size for non-existent vhosts
so you can set `-fs` correctly:
```bash
# Check baseline size — use this value for ffuf -fs
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'curl -s -o /dev/null -w "%{size_download}" http://<TARGET>/ -H "Host: nonexistent.<DOMAIN>"'

# Then fuzz with correct filter size
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'ffuf -u http://<TARGET>/ -H "Host: FUZZ.<DOMAIN>" \
   -w /usr/share/wordlists/dirb/common.txt \
   -fs <BASELINE_SIZE> -o /workspace/artifacts/ffuf-vhost.json -of json 2>&1'
```

Pass vhost to gobuster with `-H`:
```bash
wslc exec kali-pentest-<ENG_ID> bash -lc \
  'gobuster dir -u http://<TARGET>/ \
   -H "Host: <VHOST>" \
   -w /usr/share/wordlists/dirb/common.txt \
   -o /workspace/artifacts/gobuster-vhost.txt 2>&1'
```

## SSH automation with pexpect (passphrase-protected keys or interactive sudo)

When SSH requires a key passphrase or interactive sudo password, use `python3-pexpect` scripts.
ALWAYS write the script to `/workspace/artifacts/<name>.py` first and run it via
`wslc exec kali-pentest-<ENG_ID> bash -lc 'python3 /workspace/artifacts/<name>.py'`.
Inline heredoc Python via `bash -lc 'python3 << EOF ... EOF'` is fragile — shell quoting
breaks when the script contains single quotes or dollar signs.

Pattern — key passphrase + command:
```python
import pexpect

child = pexpect.spawn(
    "ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "
    "-i /tmp/id_rsa user@TARGET \"cmd1 && cmd2\""
)
child.expect("passphrase", timeout=10)
child.sendline("THE_PASSPHRASE")
child.expect(pexpect.EOF, timeout=15)
print(child.before.decode("utf-8", "ignore"))
```

Pattern — passphrase + interactive sudo:
```python
import pexpect, time

child = pexpect.spawn(
    "ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "
    "-t -i /tmp/id_rsa user@TARGET \"sudo -l\""
)
child.expect("passphrase", timeout=10)
child.sendline("KEY_PASSPHRASE")
child.expect("password for", timeout=15)
child.sendline("SUDO_PASSWORD")       # literal — pexpect sends direct to PTY, no shell expansion
child.expect(pexpect.EOF, timeout=15)
print(child.before.decode("utf-8", "ignore"))
```

Note: `child.sendline()` sends bytes directly to the PTY — dollar signs and special chars are
literal. No shell escaping needed.

## Pitfalls

1. `--rm` deletes container state; avoid for persistent engagements.
2. Missing mounts cause artifact loss inside ephemeral filesystems.
3. Running high-noise tools too early can violate rules of engagement.
4. Skill mismatch: always load the relevant tool skill before execution.
5. In this Hermes terminal backend (bash), avoid PowerShell-specific command syntax.
6. `sshpass` is not pre-installed in kali-rolling — add to bootstrap or install on demand.
7. Heredoc SSH commands (`ssh host << 'EOF'`) fail when piped through `wslc exec` because
   wslc does not allocate a PTY. Workaround: write the script to a file locally, SCP it to
   the target, then execute it remotely:
   ```bash
   # Write script locally (host-mounted workspace)
   cat > /workspace/artifacts/exploit.sh << 'SCRIPT'
   #!/bin/bash
   ...
   SCRIPT
   # SCP and execute
   sshpass -p "$PASS" scp -o StrictHostKeyChecking=no /workspace/artifacts/exploit.sh user@target:/tmp/
   sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no user@target "bash /tmp/exploit.sh"
   ```
8. `hashcat` requires OpenCL/CUDA/HIP — not available in standard wslc containers. Use `john` instead:
   - best64 rules: `john --format=Raw-SHA512 --wordlist=/usr/share/wordlists/rockyou.txt --rules=best64 hash.txt`
   - Verify best64 is registered: `grep -i best64 /etc/john/john.conf`
   - rockyou.txt ships compressed in kali — unzip first: `gunzip -k /usr/share/wordlists/rockyou.txt.gz`
9. `/usr/share/wordlists/dirb/common.txt` requires the `dirb` package, NOT just `wordlists`. Always include `dirb` in bootstrap install.
10. `sshpass` only handles password-based SSH auth (`-p`). It CANNOT supply a key passphrase — it will time out silently on the passphrase prompt. To use a passphrase-protected key, either: (a) crack the passphrase with `ssh2john` + john, then use a `pexpect` script to supply it interactively, or (b) strip the passphrase with `ssh-keygen -p -P OLD -N "" -f keyfile` once cracked.
11. `ssh2john` extracts a crackable hash from a passphrase-protected private key:
    ```bash
    ssh2john /tmp/id_rsa > /tmp/id_rsa.hash
    john --wordlist=/usr/share/wordlists/rockyou.txt /tmp/id_rsa.hash
    john --show /tmp/id_rsa.hash
    ```
12. Bash history password leaks: when `.bash_history` shows `echo "user:Pass$Word"`, the `$W` may be an unexpanded variable (empty in interactive shells without args). Test both the literal string (single-quoted) AND the expanded form. Use `ssh-keygen -p -P 'candidate' -N "" -f key` to validate each candidate without connecting to target.
13. `/c/Users/admin/pentest-workspace/...` is NOT the workspace path inside the container — the container sees it as `/workspace/...`. Always use `/workspace/artifacts/` inside wslc exec commands.
14. Heredoc multiline scripts break inside `wslc exec bash -lc '...'`; write to file and scp instead.
15. `gobuster` and `ffuf` `-fs` (filter size) must be tuned to the baseline response for the target context (vhost vs directory). Always probe a garbage subdomain first to get the default response size, then set `-fs` to that value.
16. `ftp` binary is NOT installed in kali-rolling by default. Use `curl --user anonymous:anonymous ftp://<TARGET>/file` instead. Do not waste time troubleshooting a missing `ftp` binary.
17. Command injection outputs only display the first line when piped to an HTML element. Always append `| tr "\n" "|"` to collapse multiline output before parsing via `grep "h2"`.
18. Full-path bypass for token blacklists: the filter `explode(" ", $cmd)` only checks exact bare tokens. `/bin/cat`, `/usr/bin/python3`, `/bin/bash` all pass even when `cat`, `python3`, `bash` are blocked. Always try full paths before assuming a tool is unavailable.
19. Secondary Apache virtualhost port is often firewalled (e.g. port 9001 inaccessible from outside). Read PHP source and copy files via command injection rather than fetching externally.
20. Cracked DB passwords (from MySQL/webportal hashes) often do NOT work for SSH. They may be web-portal-only credentials. Continue enumerating (steghide, other users) rather than stopping at SSH auth failure.
21. `steghide` is not in the standard bootstrap — add `apt-get install -y steghide` when stego is suspected.
22. `cp /tmp/` via cmd injection copies the file on the TARGET, not in the Kali container. To get a file from the target into Kali, base64-exfil it through the webshell response, then decode with Python in the container.
23. `sshpass` is not installed by default in kali-rolling. Install with `apt-get install -y sshpass` before attempting password-based SSH automation.
24. `python3-pexpect` is not installed by default in kali-rolling. Install with `apt-get install -y python3-pexpect` before attempting SSH automation scripts.
25. `unzip` is not installed by default in kali-rolling. Install with `apt-get install -y unzip` before attempting ZIP extraction.
26. **Host-mounted folder permissions for SSH private keys**: SSH private keys stored in Windows host-mounted folders (e.g., `/workspace/...` mapping to `C:\Users\<username>\pentest-workspace\...`) often maintain standard `777` permissions inside the container. Since NTFS mounts in WSL/Docker do not support native POSIX file permissions, executing `chmod 600` on those host-mounted paths will silently fail to restrict permissions, causing SSH to reject the key. **Workaround**: Copy the private key to a container-native directory (e.g., `cp /workspace/artifacts/id_rsa /tmp/id_rsa`) and execute `chmod 600 /tmp/id_rsa` there before using it.
27. Modern LLM providers utilize built-in safety filters that scan and automatically redact raw SSH private keys or cleartext credentials from output logs (e.g., replacing them with `[REDACTED PRIVATE KEY]`), which can corrupt retrieved keys. **Workaround**: Base64-encode the sensitive file on the target machine (e.g., `cat keyfile | base64 -w0`) to obscure the private key structure from LLM-level filters, then decode and write it directly to `/tmp/id_rsa` within your container.
28. Tools that require GPU acceleration (e.g. `hashcat`) will fail with `CL_PLATFORM_NOT_FOUND_KHR` inside wslc containers because there is no GPU/OpenCL passthrough. Do not install or run hashcat inside a wslc container. Use `john` (CPU-native) for password cracking instead. If GPU cracking is essential, run hashcat on the Windows host directly or in a native WSL2 distro with GPU drivers configured.
29. Single-quoted heredocs and nested quotes break when passed through `wslc exec kali-container bash -lc '...'`. Write scripts to a host-mounted volume file instead, then reference the file path inside the container via `/workspace/...`.

## Reference files

- `references/scope-template.md` — engagement scope and authorization checklist
- `references/logbook-template.md` — per-command evidence logging template
- `references/report-template.md` — vulnerability report skeleton

## Success criteria

- WSL + `wslc` validated.
- Persistent Kali container running with mounted host workspace.
- Pentest actions mapped to methodology phases.
- Artifacts saved to host-mounted path.
- Final report generated from reproducible evidence.

