From 89cc30bb9401707baa86827a0952e896b31a3f19 Mon Sep 17 00:00:00 2001 From: ajmallesh Date: Mon, 12 Jan 2026 17:42:06 -0800 Subject: [PATCH] refactor: replace Taskfile with bash CLI script - Add shannon bash script with start/logs/query/stop/help commands - Remove Taskfile.yml dependency (no longer requires Task installation) - Update README.md and CLAUDE.md to use ./shannon commands - Update client.ts output to show ./shannon commands --- CLAUDE.md | 29 ++++---- README.md | 21 +++--- Taskfile.yml | 94 ------------------------- shannon | 151 +++++++++++++++++++++++++++++++++++++++++ src/temporal/client.ts | 4 +- 5 files changed, 177 insertions(+), 122 deletions(-) delete mode 100644 Taskfile.yml create mode 100755 shannon diff --git a/CLAUDE.md b/CLAUDE.md index f4d0395..cbc6f70 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,6 @@ This is an AI-powered penetration testing agent designed for defensive security ### Prerequisites - **Docker** - Container runtime -- **Task** - Task runner ([Install Task](https://taskfile.dev/installation/)) - **Anthropic API key** - Set in `.env` file ### Running the Penetration Testing Agent (Docker + Temporal) @@ -22,27 +21,27 @@ cp .env.example .env # CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 # Prevents token limits during long reports # Start a pentest workflow -task start URL= REPO= +./shannon start URL= REPO= ``` Examples: ```bash -task start URL=https://example.com REPO=/path/to/repo -task start URL=https://example.com REPO=/path/to/repo CONFIG=./configs/my-config.yaml -task start URL=https://example.com REPO=/path/to/repo OUTPUT=./my-reports +./shannon start URL=https://example.com REPO=/path/to/repo +./shannon start URL=https://example.com REPO=/path/to/repo CONFIG=./configs/my-config.yaml +./shannon start URL=https://example.com REPO=/path/to/repo OUTPUT=./my-reports ``` ### Monitoring Progress ```bash -task logs # View real-time worker logs -task query ID= # Query specific workflow progress +./shannon logs # View real-time worker logs +./shannon query ID= # Query specific workflow progress # Temporal Web UI available at http://localhost:8233 ``` ### Stopping Shannon ```bash -task stop # Stop containers (preserves workflow data) -task stop CLEAN=true # Full cleanup including volumes +./shannon stop # Stop containers (preserves workflow data) +./shannon stop CLEAN=true # Full cleanup including volumes ``` ### Options @@ -68,7 +67,7 @@ TOTP generation is handled automatically via the `generate_totp` MCP tool during npm run build # Run with pipeline testing mode (fast, minimal deliverables) -task start URL= REPO= PIPELINE_TESTING=true +./shannon start URL= REPO= PIPELINE_TESTING=true ``` ## Architecture & Components @@ -94,7 +93,7 @@ Shannon uses Temporal for durable workflow orchestration: Key features: - **Crash recovery** - Workflows resume automatically after worker restart -- **Queryable progress** - Real-time status via `task query` or Temporal Web UI +- **Queryable progress** - Real-time status via `./shannon query` or Temporal Web UI - **Intelligent retry** - Distinguishes transient vs permanent errors - **Parallel execution** - 5 concurrent agents in vulnerability/exploitation phases @@ -243,7 +242,7 @@ The application uses a comprehensive error handling system with: ### Testing Mode The agent includes a testing mode that skips external tool execution for faster development cycles: ```bash -task start URL= REPO= PIPELINE_TESTING=true +./shannon start URL= REPO= PIPELINE_TESTING=true ``` ### Security Focus @@ -271,7 +270,7 @@ The tool should only be used on systems you own or have explicit permission to t - `src/audit/` - Crash-safe logging and metrics system **Configuration:** -- `Taskfile.yml` - Task runner commands +- `shannon` - CLI script for running pentests - `docker-compose.yml` - Temporal server + worker containers - `configs/` - YAML configs with `config-schema.json` for validation - `prompts/` - AI prompt templates (`vuln-*.txt`, `exploit-*.txt`, etc.) @@ -289,7 +288,7 @@ The tool should only be used on systems you own or have explicit permission to t ### Temporal & Docker Issues - **"Temporal not ready"**: Wait for health check or run `docker compose logs temporal` - **Worker not processing**: Ensure worker container is running with `docker compose ps` -- **Reset workflow state**: `task stop CLEAN=true` removes all Temporal data and volumes +- **Reset workflow state**: `./shannon stop CLEAN=true` removes all Temporal data and volumes - **Local apps unreachable**: Use `host.docker.internal` instead of `localhost` for URLs - **Container permissions**: On Linux, may need `sudo` for docker commands @@ -308,4 +307,4 @@ Missing tools can be skipped using `PIPELINE_TESTING=true` mode during developme open http://localhost:8233 ``` -Note: For recovery from corrupted state, simply delete `.shannon-store.json` or run `task stop CLEAN=true`. +Note: For recovery from corrupted state, simply delete `.shannon-store.json` or run `./shannon stop CLEAN=true`. diff --git a/README.md b/README.md index 897478b..bac7fd5 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,6 @@ Shannon is available in two editions: ### Prerequisites - **Docker** - Container runtime ([Install Docker](https://docs.docker.com/get-docker/)) -- **Task** - Task runner for simplified commands ([Install Task](https://taskfile.dev/installation/)) - **Anthropic API key or Claude Code OAuth token** - Get from [Anthropic Console](https://console.anthropic.com) ### Quick Start @@ -123,7 +122,7 @@ CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000 EOF # 3. Run a pentest -task start URL=https://your-app.com REPO=/path/to/your/repo +./shannon start URL=https://your-app.com REPO=/path/to/your/repo ``` Shannon will build the containers, start the workflow, and return a workflow ID. The pentest runs in the background. @@ -132,10 +131,10 @@ Shannon will build the containers, start the workflow, and return a workflow ID. ```bash # View real-time worker logs -task logs +./shannon logs # Query a specific workflow's progress -task query ID=shannon-1234567890 +./shannon query ID=shannon-1234567890 # Open the Temporal Web UI for detailed monitoring open http://localhost:8233 @@ -145,23 +144,23 @@ open http://localhost:8233 ```bash # Stop all containers (preserves workflow data) -task stop +./shannon stop # Full cleanup (removes all data) -task stop CLEAN=true +./shannon stop CLEAN=true ``` ### Usage Examples ```bash # Basic pentest -task start URL=https://example.com REPO=/path/to/repo +./shannon start URL=https://example.com REPO=/path/to/repo # With a configuration file -task start URL=https://example.com REPO=/path/to/repo CONFIG=./configs/my-config.yaml +./shannon start URL=https://example.com REPO=/path/to/repo CONFIG=./configs/my-config.yaml # Custom output directory -task start URL=https://example.com REPO=/path/to/repo OUTPUT=./my-reports +./shannon start URL=https://example.com REPO=/path/to/repo OUTPUT=./my-reports ``` ### Prepare Your Repository @@ -191,7 +190,7 @@ git clone https://github.com/your-org/api.git **For Linux (Native Docker):** -You may need to run Task commands with `sudo` depending on your Docker setup. If you encounter permission issues with output files, ensure your user has access to the Docker socket. +You may need to run commands with `sudo` depending on your Docker setup. If you encounter permission issues with output files, ensure your user has access to the Docker socket. **For macOS:** @@ -202,7 +201,7 @@ Works out of the box with Docker Desktop installed. Docker containers cannot reach `localhost` on your host machine. Use `host.docker.internal` in place of `localhost`: ```bash -task start URL=http://host.docker.internal:3000 REPO=/path/to/repo +./shannon start URL=http://host.docker.internal:3000 REPO=/path/to/repo ``` ### Configuration (Optional) diff --git a/Taskfile.yml b/Taskfile.yml deleted file mode 100644 index 61cf6c7..0000000 --- a/Taskfile.yml +++ /dev/null @@ -1,94 +0,0 @@ -version: '3' - -dotenv: ['.env'] - -vars: - COMPOSE_FILE: docker-compose.yml - -tasks: - default: - silent: true - cmds: [task help] - - help: - desc: Show usage information - silent: true - cmds: - - | - echo "Shannon - AI Penetration Testing Framework" - echo "" - echo "Usage:" - echo " task start URL= REPO= Start a pentest workflow" - echo " task logs View real-time worker logs" - echo " task query ID= Query workflow progress" - echo " task stop Stop all containers" - echo " task help Show this help message" - echo "" - echo "Options for 'start':" - echo " CONFIG= Configuration file (YAML)" - echo " OUTPUT= Output directory for reports" - echo "" - echo "Options for 'stop':" - echo " CLEAN=true Remove all data including volumes" - echo "" - echo "Examples:" - echo " task start URL=https://example.com REPO=/path/to/repo" - echo " task start URL=https://example.com REPO=/path/to/repo CONFIG=./config.yaml" - echo " task query ID=shannon-1234567890" - echo " task stop CLEAN=true" - echo "" - echo "Monitor workflows at http://localhost:8233" - - start: - desc: Start a pentest workflow - silent: true - requires: - vars: [URL, REPO] - cmds: - - | - if [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then - echo "ERROR: Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env" - exit 1 - fi - - TARGET_REPO={{.REPO}} docker compose -f {{.COMPOSE_FILE}} up -d --build - - | - for i in $(seq 1 30); do - docker compose -f {{.COMPOSE_FILE}} exec -T temporal \ - temporal operator cluster health --address localhost:7233 2>/dev/null | grep -q "SERVING" && break - [ $i -eq 30 ] && echo "Timeout waiting for Temporal" && exit 1 - sleep 2 - done - - | - ARGS="" - {{if .CONFIG}}ARGS="$ARGS --config {{.CONFIG}}"{{end}} - {{if .OUTPUT}}ARGS="$ARGS --output {{.OUTPUT}}"{{end}} - {{if eq .PIPELINE_TESTING "true"}}ARGS="$ARGS --pipeline-testing"{{end}} - docker compose -f {{.COMPOSE_FILE}} exec -T worker \ - node dist/temporal/client.js "{{.URL}}" "/target-repo" $ARGS {{.CLI_ARGS}} - - logs: - desc: View real-time worker logs - silent: true - cmds: - - docker compose -f {{.COMPOSE_FILE}} logs -f worker {{.CLI_ARGS}} - - query: - desc: Query workflow progress - silent: true - requires: - vars: [ID] - cmds: - - | - docker compose -f {{.COMPOSE_FILE}} exec -T worker \ - node dist/temporal/query.js "{{.ID}}" - - stop: - desc: Stop all containers - silent: true - cmds: - - | - {{if eq .CLEAN "true"}} - docker compose -f {{.COMPOSE_FILE}} down -v - {{else}} - docker compose -f {{.COMPOSE_FILE}} down - {{end}} diff --git a/shannon b/shannon new file mode 100755 index 0000000..651d94e --- /dev/null +++ b/shannon @@ -0,0 +1,151 @@ +#!/bin/bash +# Shannon CLI - AI Penetration Testing Framework + +set -e + +COMPOSE_FILE="docker-compose.yml" + +# Load .env if present +if [ -f .env ]; then + set -a + source .env + set +a +fi + +show_help() { + cat << 'EOF' +Shannon - AI Penetration Testing Framework + +Usage: + ./shannon start URL= REPO= Start a pentest workflow + ./shannon logs View real-time worker logs + ./shannon query ID= Query workflow progress + ./shannon stop Stop all containers + ./shannon help Show this help message + +Options for 'start': + CONFIG= Configuration file (YAML) + OUTPUT= Output directory for reports + PIPELINE_TESTING=true Use minimal prompts for fast testing + +Options for 'stop': + CLEAN=true Remove all data including volumes + +Examples: + ./shannon start URL=https://example.com REPO=/path/to/repo + ./shannon start URL=https://example.com REPO=/path/to/repo CONFIG=./config.yaml + ./shannon query ID=shannon-1234567890 + ./shannon stop CLEAN=true + +Monitor workflows at http://localhost:8233 +EOF +} + +# Parse KEY=value arguments into variables +parse_args() { + for arg in "$@"; do + case "$arg" in + URL=*) URL="${arg#URL=}" ;; + REPO=*) REPO="${arg#REPO=}" ;; + CONFIG=*) CONFIG="${arg#CONFIG=}" ;; + OUTPUT=*) OUTPUT="${arg#OUTPUT=}" ;; + ID=*) ID="${arg#ID=}" ;; + CLEAN=*) CLEAN="${arg#CLEAN=}" ;; + PIPELINE_TESTING=*) PIPELINE_TESTING="${arg#PIPELINE_TESTING=}" ;; + esac + done +} + +cmd_start() { + parse_args "$@" + + # Validate required vars + if [ -z "$URL" ] || [ -z "$REPO" ]; then + echo "ERROR: URL and REPO are required" + echo "Usage: ./shannon start URL= REPO=" + exit 1 + fi + + # Check for API key + if [ -z "$ANTHROPIC_API_KEY" ] && [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then + echo "ERROR: Set ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN in .env" + exit 1 + fi + + # Start containers + TARGET_REPO="$REPO" docker compose -f "$COMPOSE_FILE" up -d --build + + # Wait for Temporal to be ready + echo "Waiting for Temporal to be ready..." + for i in $(seq 1 30); do + if docker compose -f "$COMPOSE_FILE" exec -T temporal \ + temporal operator cluster health --address localhost:7233 2>/dev/null | grep -q "SERVING"; then + break + fi + if [ "$i" -eq 30 ]; then + echo "Timeout waiting for Temporal" + exit 1 + fi + sleep 2 + done + + # Build optional args + ARGS="" + [ -n "$CONFIG" ] && ARGS="$ARGS --config $CONFIG" + [ -n "$OUTPUT" ] && ARGS="$ARGS --output $OUTPUT" + [ "$PIPELINE_TESTING" = "true" ] && ARGS="$ARGS --pipeline-testing" + + # Run the client + docker compose -f "$COMPOSE_FILE" exec -T worker \ + node dist/temporal/client.js "$URL" "/target-repo" $ARGS +} + +cmd_logs() { + docker compose -f "$COMPOSE_FILE" logs -f worker "$@" +} + +cmd_query() { + parse_args "$@" + + if [ -z "$ID" ]; then + echo "ERROR: ID is required" + echo "Usage: ./shannon query ID=" + exit 1 + fi + + docker compose -f "$COMPOSE_FILE" exec -T worker \ + node dist/temporal/query.js "$ID" +} + +cmd_stop() { + parse_args "$@" + + if [ "$CLEAN" = "true" ]; then + docker compose -f "$COMPOSE_FILE" down -v + else + docker compose -f "$COMPOSE_FILE" down + fi +} + +# Main command dispatch +case "${1:-help}" in + start) + shift + cmd_start "$@" + ;; + logs) + shift + cmd_logs "$@" + ;; + query) + shift + cmd_query "$@" + ;; + stop) + shift + cmd_stop "$@" + ;; + help|--help|-h|*) + show_help + ;; +esac diff --git a/src/temporal/client.ts b/src/temporal/client.ts index e16e711..73936a9 100644 --- a/src/temporal/client.ts +++ b/src/temporal/client.ts @@ -163,8 +163,8 @@ async function startPipeline(): Promise { if (!waitForCompletion) { console.log(chalk.bold('Monitor progress:')); console.log(chalk.white(' Web UI: ') + chalk.blue(`http://localhost:8233/namespaces/default/workflows/${workflowId}`)); - console.log(chalk.white(' Logs: ') + chalk.gray('task logs')); - console.log(chalk.white(' Query: ') + chalk.gray(`task query ID=${workflowId}`)); + console.log(chalk.white(' Logs: ') + chalk.gray('./shannon logs')); + console.log(chalk.white(' Query: ') + chalk.gray(`./shannon query ID=${workflowId}`)); console.log(); return; }