Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ AI-powered penetration testing agent for defensive security analysis. Automates
# Setup
cp .env.example .env && edit .env # Set ANTHROPIC_API_KEY

# Prepare repo (REPO is a folder name inside ./repos/, not an absolute path)
# Prepare repo (clone into ./repos/, or point to any path)
git clone https://github.com/org/repo.git ./repos/my-repo
# or symlink: ln -s /path/to/existing/repo ./repos/my-repo

# Run
./shannon start URL=<url> REPO=my-repo
./shannon start URL=<url> REPO=my-repo # Option A: folder name in ./repos/
./shannon start URL=<url> REPO=/path/to/existing/repo # Option B: absolute path
./shannon start URL=<url> REPO=../other-project/repo # Option C: relative path
./shannon start URL=<url> REPO=my-repo CONFIG=./configs/my-config.yaml

# Workspaces & Resume
Expand Down Expand Up @@ -150,7 +151,7 @@ Comments must be **timeless** — no references to this conversation, refactorin

## Troubleshooting

- **"Repository not found"** — `REPO` must be a folder name inside `./repos/`, not an absolute path. Clone or symlink your repo there first: `ln -s /path/to/repo ./repos/my-repo`
- **"Repository not found"** — `REPO` can be a folder name inside `./repos/`, an absolute path, or a relative path. Verify the path exists and contains a git repository.
- **"Temporal not ready"** — Wait for health check or `docker compose logs temporal`
- **Worker not processing** — Check `docker compose ps`
- **Reset state** — `./shannon stop CLEAN=true`
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ RUN npm prune --production && \
RUN npm install -g @anthropic-ai/claude-code

# Create directories for session data and ensure proper permissions
RUN mkdir -p /app/sessions /app/deliverables /app/repos /app/configs && \
RUN mkdir -p /app/sessions /app/deliverables /app/repos /app/configs /target-repo && \
mkdir -p /tmp/.cache /tmp/.config /tmp/.npm && \
chmod 777 /app && \
chmod 777 /app /target-repo && \
chmod 777 /tmp/.cache && \
chmod 777 /tmp/.config && \
chmod 777 /tmp/.npm && \
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ CLAUDE_CODE_MAX_OUTPUT_TOKENS=64000
EOF

# 3. Run a pentest
# Using ./repos/ directory
./shannon start URL=https://your-app.com REPO=your-repo

# Or point to any directory on your filesystem
./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.
Expand Down Expand Up @@ -202,6 +206,9 @@ open http://localhost:8233
# Basic pentest
./shannon start URL=https://example.com REPO=repo-name

# Pentest with arbitrary repo path
./shannon start URL=https://example.com REPO=/home/user/projects/my-app

# With a configuration file
./shannon start URL=https://example.com REPO=repo-name CONFIG=./configs/my-config.yaml

Expand Down Expand Up @@ -244,10 +251,23 @@ Shannon supports **workspaces** that allow you to resume interrupted or failed r

### Prepare Your Repository

Shannon expects target repositories to be placed under the `./repos/` directory at the project root. The `REPO` flag refers to a folder name inside `./repos/`. Copy the repository you want to scan into `./repos/`, or clone it directly there:
Shannon can analyze a repository from any location on your host filesystem. Pass the path to your repo via the `REPO` flag:

```bash
# Point to a repo anywhere on your filesystem
./shannon start URL=https://example.com REPO=/home/user/projects/my-app

# Or use a relative path
./shannon start URL=https://example.com REPO=../other-project/my-app
```

**Using the `./repos/` directory (optional):**

You can also clone repos into the `./repos/` directory and reference them by folder name:

```bash
git clone https://github.com/your-org/your-repo.git ./repos/your-repo
./shannon start URL=https://example.com REPO=your-repo
```

**For monorepos:**
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ services:
- ./audit-logs:/app/audit-logs
- ${OUTPUT_DIR:-./audit-logs}:/app/output
- ./credentials:/app/credentials:ro
- ./repos:/repos
- ${REPO_MOUNT_SOURCE:-./repos}:${REPO_MOUNT_TARGET:-/repos}
- ${BENCHMARKS_BASE:-.}:/benchmarks
shm_size: 2gb
ipc: host
Expand Down
46 changes: 36 additions & 10 deletions shannon
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ show_help() {
AI Penetration Testing Framework

Usage:
./shannon start URL=<url> REPO=<name> Start a pentest workflow
./shannon start URL=<url> REPO=<path> Start a pentest workflow
./shannon workspaces List all workspaces
./shannon logs ID=<workflow-id> Tail logs for a specific workflow
./shannon stop Stop all containers
./shannon help Show this help message

Options for 'start':
REPO=<name> Folder name under ./repos/ (e.g. REPO=repo-name)
REPO=<path> Repository path: folder name under ./repos/, absolute, or relative path
CONFIG=<path> Configuration file (YAML)
OUTPUT=<path> Output directory for reports (default: ./audit-logs/)
WORKSPACE=<name> Named workspace (auto-resumes if exists, creates if new)
Expand All @@ -59,6 +59,8 @@ Options for 'stop':

Examples:
./shannon start URL=https://example.com REPO=repo-name
./shannon start URL=https://example.com REPO=/home/user/projects/my-app
./shannon start URL=https://example.com REPO=../other-project/my-app
./shannon start URL=https://example.com REPO=repo-name WORKSPACE=q1-audit
./shannon start URL=https://example.com REPO=repo-name CONFIG=./config.yaml
./shannon start URL=https://example.com REPO=repo-name OUTPUT=./my-reports
Expand Down Expand Up @@ -96,10 +98,10 @@ is_temporal_ready() {

# Ensure containers are running with correct mounts
ensure_containers() {
# If custom OUTPUT_DIR is set, always refresh worker to ensure correct volume mount
# If custom mounts are set, always refresh worker to ensure correct volume mounts
# Docker compose will only recreate if the mount actually changed
if [ -n "$OUTPUT_DIR" ]; then
echo "Ensuring worker has correct output mount..."
if [ -n "$OUTPUT_DIR" ] || [ -n "$REPO_MOUNT_SOURCE" ]; then
echo "Ensuring worker has correct volume mounts..."
docker compose -f "$COMPOSE_FILE" $COMPOSE_OVERRIDE up -d worker 2>/dev/null || true
fi

Expand Down Expand Up @@ -138,7 +140,7 @@ cmd_start() {
# Validate required vars
if [ -z "$URL" ] || [ -z "$REPO" ]; then
echo "ERROR: URL and REPO are required"
echo "Usage: ./shannon start URL=<url> REPO=<name>"
echo "Usage: ./shannon start URL=<url> REPO=<path>"
exit 1
fi

Expand Down Expand Up @@ -193,17 +195,36 @@ cmd_start() {
fi

# Determine container path for REPO
# - If REPO is already a container path (/benchmarks/*, /repos/*), use as-is
# - Otherwise, treat as a folder name under ./repos/ (mounted at /repos in container)
# Mode 1: Already a container path (/benchmarks/*, /repos/*) — use as-is
# Mode 2: Host filesystem path (contains / or starts with .) — resolve and mount
# Mode 3: Simple name — legacy behavior, folder under ./repos/
case "$REPO" in
/benchmarks/*|/repos/*)
# Container path — use as-is (e.g., internal benchmarks)
CONTAINER_REPO="$REPO"
;;
/*|./*|../*|..|.)
# Absolute or relative path — resolve to absolute and mount into container
REPO_HOST_PATH="$(cd "$REPO" 2>/dev/null && pwd -P)" || {
echo "ERROR: Repository not found at $REPO"
echo ""
echo "Provide an absolute path, a relative path, or a folder name under ./repos/"
exit 1
}
export REPO_MOUNT_SOURCE="$REPO_HOST_PATH"
export REPO_MOUNT_TARGET="/target-repo"
CONTAINER_REPO="/target-repo"
;;
*)
# Simple name — legacy behavior: folder under ./repos/
unset REPO_MOUNT_SOURCE REPO_MOUNT_TARGET
if [ ! -d "./repos/$REPO" ]; then
echo "ERROR: Repository not found at ./repos/$REPO"
echo ""
echo "Place your target repository under the ./repos/ directory"
echo "Provide a folder name under ./repos/, or use an absolute/relative path:"
echo " REPO=my-repo (looks in ./repos/my-repo)"
echo " REPO=/home/user/projects/repo (absolute path)"
echo " REPO=../other-project/repo (relative path)"
exit 1
fi
CONTAINER_REPO="/repos/$REPO"
Expand Down Expand Up @@ -251,7 +272,12 @@ cmd_start() {
chmod 777 ./audit-logs

# Ensure repo deliverables directory is writable by container user (UID 1001)
if [ -d "./repos/$REPO" ]; then
if [ -n "$REPO_HOST_PATH" ]; then
# Arbitrary path mode — use resolved host path
mkdir -p "$REPO_HOST_PATH/deliverables"
chmod 777 "$REPO_HOST_PATH/deliverables"
elif [ -d "./repos/$REPO" ]; then
# Legacy mode — folder under ./repos/
mkdir -p "./repos/$REPO/deliverables"
chmod 777 "./repos/$REPO/deliverables"
fi
Expand Down