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
26 changes: 13 additions & 13 deletions .github/workflows/release-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

steps:
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@49933ea5288caeca8642195b5c3ad3664e5088c2 # v6
with:
node-version: 24
registry-url: https://registry.npmjs.org
Expand Down Expand Up @@ -61,20 +61,20 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v6

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v4

- name: Log in to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v7
with:
context: .
platforms: ${{ matrix.platform }}
Expand All @@ -89,7 +89,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v6
with:
name: digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
Expand All @@ -108,17 +108,17 @@ jobs:

steps:
- name: Download digests
uses: actions/download-artifact@v6
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v6
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v4

- name: Log in to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand All @@ -138,7 +138,7 @@ jobs:
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"

- name: Install cosign
uses: sigstore/cosign-installer@v4.1.0
uses: sigstore/cosign-installer@3454372be43e8dfc343a2f44a3a73ed6db44bcb4 # v4.1.0

- name: Sign Docker image
run: cosign sign --yes "keygraph/shannon@${{ steps.inspect.outputs.digest }}"
Expand All @@ -161,13 +161,13 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v6

- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4

- name: Configure npm registry
uses: actions/setup-node@v6
uses: actions/setup-node@49933ea5288caeca8642195b5c3ad3664e5088c2 # v6
with:
node-version: 24
registry-url: https://registry.npmjs.org
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rollback-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@49933ea5288caeca8642195b5c3ad3664e5088c2 # v6
with:
node-version: 24
registry-url: https://registry.npmjs.org
Expand Down
16 changes: 8 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Uses Chainguard Wolfi for minimal attack surface and supply chain security

# Builder stage - Install tools and dependencies
FROM cgr.dev/chainguard/wolfi-base:latest AS builder
FROM cgr.dev/chainguard/wolfi-base:20250319 AS builder

# Install system dependencies available in Wolfi
RUN apk update && apk add --no-cache \
Expand Down Expand Up @@ -38,7 +38,7 @@ ENV CGO_ENABLED=1
RUN mkdir -p $GOPATH/bin

# Install Go-based security tools
RUN go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
RUN go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@v2.6.8
# Install WhatWeb from GitHub (Ruby-based tool)
RUN git clone --depth 1 https://github.com/urbanadventurer/WhatWeb.git /opt/whatweb && \
chmod +x /opt/whatweb/whatweb && \
Expand All @@ -51,7 +51,7 @@ RUN git clone --depth 1 https://github.com/urbanadventurer/WhatWeb.git /opt/what
RUN pip3 install --no-cache-dir schemathesis

# Runtime stage - Minimal production image
FROM cgr.dev/chainguard/wolfi-base:latest AS runtime
FROM cgr.dev/chainguard/wolfi-base:20250319 AS runtime

# Install only runtime dependencies
USER root
Expand Down Expand Up @@ -132,10 +132,10 @@ 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 && \
mkdir -p /tmp/.cache /tmp/.config /tmp/.npm && \
chmod 777 /app && \
chmod 777 /tmp/.cache && \
chmod 777 /tmp/.config && \
chmod 777 /tmp/.npm && \
chmod 750 /app && \
chmod 750 /tmp/.cache && \
chmod 750 /tmp/.config && \
chmod 750 /tmp/.npm && \
chown -R pentest:pentest /app

# Switch to non-root user
Expand All @@ -155,7 +155,7 @@ ENV XDG_CONFIG_HOME=/tmp/.config
# Configure Git identity and trust all directories
RUN git config --global user.email "agent@localhost" && \
git config --global user.name "Pentest Agent" && \
git config --global --add safe.directory '*'
git config --global --add safe.directory /app && git config --global --add safe.directory /repos

# Set entrypoint
ENTRYPOINT ["node", "dist/shannon.js"]
6 changes: 3 additions & 3 deletions configs/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ authentication:
login_type: form # Options: 'form' or 'sso'
login_url: "https://example.com/login"
credentials:
username: "testuser"
password: "testpassword"
totp_secret: "JBSWY3DPEHPK3PXP" # Optional TOTP secret for 2FA
username: "CHANGE_ME_USERNAME" # REQUIRED: Replace with your actual username
password: "CHANGE_ME_PASSWORD" # REQUIRED: Replace with your actual password
totp_secret: "CHANGE_ME_SECRET" # Optional: Replace with your base32 TOTP secret for 2FA

# Natural language instructions for login flow
login_flow:
Expand Down
6 changes: 3 additions & 3 deletions configs/router-config.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"HOST": "0.0.0.0",
"APIKEY": "shannon-router-key",
"APIKEY": "$SHANNON_ROUTER_APIKEY",
"LOG": true,
"LOG_LEVEL": "info",
"LOG_LEVEL": "warn",
"NON_INTERACTIVE_MODE": true,
"API_TIMEOUT_MS": 600000,
"API_TIMEOUT_MS": 60000,
"Providers": [
{
"name": "openai",
Expand Down
14 changes: 10 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
services:
temporal:
image: temporalio/temporal:latest
image: temporalio/temporal:1.26.2
command: ["server", "start-dev", "--db-filename", "/home/temporal/temporal.db", "--ip", "0.0.0.0"]
ports:
- "127.0.0.1:7233:7233" # gRPC
Expand Down Expand Up @@ -46,9 +46,15 @@ services:
- ./credentials:/app/credentials:ro
- ./repos:/repos
- ${BENCHMARKS_BASE:-.}:/benchmarks
shm_size: 2gb
security_opt:
- seccomp:unconfined
shm_size: 512mb
deploy:
resources:
limits:
cpus: '4'
memory: 8G
reservations:
cpus: '1'
memory: 2G

# Optional: claude-code-router for multi-model support
# Start with: ROUTER=true ./shannon start ...
Expand Down
1 change: 1 addition & 0 deletions mcp-server/src/tools/generate-totp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const GenerateTotpInputSchema = z.object({
secret: z
.string()
.min(1)
.max(256, 'Secret too long')
.regex(/^[A-Z2-7]+$/i, 'Must be base32-encoded')
.describe('Base32-encoded TOTP secret'),
});
Expand Down
17 changes: 15 additions & 2 deletions mcp-server/src/tools/save-deliverable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,21 @@ export type SaveDeliverableInput = z.infer<typeof SaveDeliverableInputSchema>;
* Prevents path traversal attacks (e.g., ../../../etc/passwd).
*/
function isPathContained(basePath: string, targetPath: string): boolean {
const resolvedBase = path.resolve(basePath);
const resolvedTarget = path.resolve(targetPath);
let resolvedBase: string;
let resolvedTarget: string;
try {
resolvedBase = fs.realpathSync(path.resolve(basePath));
} catch {
resolvedBase = path.resolve(basePath);
}
try {
// For new files, resolve the parent directory through symlinks
const parentDir = path.dirname(path.resolve(targetPath));
const parentReal = fs.realpathSync(parentDir);
resolvedTarget = path.join(parentReal, path.basename(targetPath));
} catch {
resolvedTarget = path.resolve(targetPath);
}
return resolvedTarget === resolvedBase || resolvedTarget.startsWith(resolvedBase + path.sep);
}

Expand Down
17 changes: 13 additions & 4 deletions mcp-server/src/utils/file-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
* Ported from tools/save_deliverable.js (lines 117-130).
*/

import { writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import { writeFileSync, mkdirSync, renameSync, unlinkSync } from 'fs';
import { join, dirname } from 'path';
import { randomBytes } from 'crypto';

/**
* Save deliverable file to deliverables/ directory
Expand All @@ -32,8 +33,16 @@ export function saveDeliverableFile(targetDir: string, filename: string, content
throw new Error(`Cannot create deliverables directory at ${deliverablesDir}`);
}

// Write file (atomic write - single operation)
writeFileSync(filepath, content, 'utf8');
// Atomic write: write to temp file then rename
const tempPath = join(deliverablesDir, `.tmp-${randomBytes(8).toString('hex')}`);
try {
writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o644 });
renameSync(tempPath, filepath);
} catch (err) {
// Clean up temp file on failure
try { unlinkSync(tempPath); } catch { /* ignore */ }
throw err;
}

return filepath;
}
2 changes: 1 addition & 1 deletion mcp-server/src/validation/queue-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface ValidationResult {
*/
export function validateQueueJson(content: string): ValidationResult {
try {
const parsed = JSON.parse(content) as unknown;
const parsed = JSON.parse(content, (key, value) => key === '__proto__' || key === 'constructor' ? undefined : value) as unknown;

// Type guard for the parsed result
if (typeof parsed !== 'object' || parsed === null) {
Expand Down
Loading