#!/usr/bin/env bash # Basic penetration testing for AgentHub # Run before each release set -e BASE_URL="${TEST_URL:-http://localhost:3000}" PASS=0 FAIL=0 echo "=== AgentHub Pen-Test Suite ===" echo "Target: $BASE_URL" echo "" # Helper functions pass() { echo "✅ PASS: $1" PASS=$((PASS + 1)) } fail() { echo "❌ FAIL: $1" FAIL=$((FAIL + 1)) } test_endpoint() { local method=$1 local path=$2 local data=$3 local expected_status=$4 local test_name=$5 local actual_status if [ -n "$data" ]; then actual_status=$(curl -s -o /dev/null -w "%{http_code}" \ -X "$method" \ -H "Content-Type: application/json" \ -d "$data" \ "$BASE_URL$path") else actual_status=$(curl -s -o /dev/null -w "%{http_code}" \ -X "$method" \ "$BASE_URL$path") fi if [ "$actual_status" -eq "$expected_status" ]; then pass "$test_name (HTTP $actual_status)" else fail "$test_name (expected $expected_status, got $actual_status)" fi } echo "=== 1. SQL Injection Tests ===" # Test agent creation with SQL injection test_endpoint POST "/api/v1/agents" \ '{"name":"test'"'"' OR '"'"'1'"'"'='"'"'1","displayName":"SQLi Test","role":"agent"}' \ 400 \ "SQL injection in agent name rejected" # Test session creation with malicious token test_endpoint POST "/api/v1/sessions" \ '{"apiToken":"ah_live_XXXX'"'"'; DROP TABLE agents--"}' \ 401 \ "SQL injection in token rejected" echo "" echo "=== 2. Header Injection Tests ===" # Test XSS in headers test_endpoint GET "/rooms" "" 401 "Missing auth header returns 401" actual=$(curl -s -H "x-agent-id: " "$BASE_URL/rooms" | grep -o "error" || echo "") if [ -n "$actual" ]; then pass "XSS in x-agent-id header rejected" else fail "XSS in x-agent-id header not properly rejected" fi echo "" echo "=== 3. Rate Limit Tests ===" # Burst 10 requests to healthz (should pass, allowlisted) success_count=0 for i in {1..10}; do status=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/healthz") if [ "$status" -eq 200 ]; then success_count=$((success_count + 1)) fi done if [ $success_count -eq 10 ]; then pass "Healthz endpoint not rate-limited" else fail "Healthz endpoint incorrectly rate-limited ($success_count/10 succeeded)" fi # Burst 120 unauthenticated requests to /rooms (should hit rate limit) # Rate limit is 100 req/min, so 120 should fail on some rate_limited=0 for i in {1..120}; do status=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/rooms") if [ "$status" -eq 429 ]; then rate_limited=1 break fi done if [ $rate_limited -eq 1 ]; then pass "Unauthenticated rate limit enforced (100 req/min)" else fail "Unauthenticated rate limit not enforced (expected 429 after 100 requests)" fi echo "" echo "=== 4. CORS Tests ===" # Test unauthorized origin cors_header=$(curl -s -H "Origin: http://evil.com" -I "$BASE_URL/healthz" | grep -i "access-control-allow-origin" || echo "") if [ -z "$cors_header" ]; then pass "Unauthorized origin rejected by CORS" else fail "Unauthorized origin accepted by CORS: $cors_header" fi # Test allowed origin (localhost) cors_header=$(curl -s -H "Origin: http://localhost:3000" -I "$BASE_URL/healthz" | grep -i "access-control-allow-origin" || echo "") if [ -n "$cors_header" ]; then pass "Allowed origin accepted by CORS" else fail "Allowed origin rejected by CORS" fi echo "" echo "=== 5. Security Headers Tests ===" # Check for security headers headers=$(curl -s -I "$BASE_URL/healthz") echo "$headers" | grep -q "x-frame-options: DENY" && pass "X-Frame-Options header present" || fail "X-Frame-Options header missing" echo "$headers" | grep -q "referrer-policy: strict-origin" && pass "Referrer-Policy header present" || fail "Referrer-Policy header missing" echo "$headers" | grep -q "content-security-policy:" && pass "CSP header present" || fail "CSP header missing" # HSTS should be absent in Phase 1 (HTTP LAN) echo "$headers" | grep -q "strict-transport-security:" && fail "HSTS enabled in Phase 1 (should be disabled)" || pass "HSTS disabled in Phase 1" echo "" echo "=== 6. Input Validation Tests ===" # Test invalid UUID in room creation test_endpoint POST "/rooms" \ '{"slug":"test-room","name":"Test","members":["not-a-uuid"]}' \ 400 \ "Invalid UUID in members rejected" # Test oversized message body (16KB limit) oversized_body=$(printf 'A%.0s' {1..20000}) test_endpoint POST "/api/v1/sessions" \ "{\"apiToken\":\"$oversized_body\"}" \ 401 \ "Oversized payload handled gracefully" echo "" echo "=== Summary ===" echo "Passed: $PASS" echo "Failed: $FAIL" if [ $FAIL -eq 0 ]; then echo "✅ All pen-tests passed" exit 0 else echo "❌ Some pen-tests failed" exit 1 fi