@shinyaz

AWS Security Agent Verification — On-Demand Penetration Testing Detection Quality and Cost

Table of Contents

Introduction

On March 31, 2026, AWS announced the general availability of AWS Security Agent's on-demand penetration testing. It's a "frontier agent" that autonomously discovers and validates application vulnerabilities, claiming to "transform security testing from weeks to hours."

AWS Security Agent offers three capabilities:

  1. Penetration Testing — On-demand AI-driven pen testing ($50/task-hour) [GA]
  2. Design Security Review — Real-time security feedback on design documents (free, 200/month) [Preview]
  3. Code Security Review — Automated PR security reviews (free, 1,000/month) [Preview]

Only penetration testing reached GA with this launch — Design Security Review and Code Security Review remain in Preview. This article verifies the GA feature: penetration testing. It claims to detect not only OWASP Top 10 but also business logic flaws — but does the detection quality justify $50/task-hour? This article deploys a Flask app with 5 intentional vulnerabilities and evaluates detection rate, finding quality, and cost. Official docs: AWS Security Agent User Guide.

Prerequisites:

  • AWS CLI v2 (verified with 2.34.21)
  • IAM role creation permissions, EC2/VPC access
  • Test region: ap-northeast-1 (Tokyo)
  • Pricing: $50/task-hour (per-second billing). New customers get a 2-month free trial (200 task-hours/month)

Available in 6 regions: us-east-1, us-west-2, eu-west-1, eu-central-1, ap-southeast-2, ap-northeast-1.

Skip to Summary for results only.

Verification 1: CLI Setup (Agent Space to Pentest Creation)

Security Agent has a 3-layer resource model. Application is the account-wide Security Agent configuration (one per account, auto-created during initial console setup). Agent Space is a logical container that groups test targets, associating VPCs, IAM roles, and Target Domains. Target Domain is a registered domain authorized for testing, requiring ownership proof.

This verification created an Agent Space and completed the full flow from target registration to pentest creation using CLI only. Note that steps 3-5 require the EC2 private DNS and VPC information, so run the "EC2 deployment steps" in Verification 2 first to obtain $VPC_ID, $SUBNET_ID, $SG_ID, and $PRIVATE_DNS, then return to this section. Setup consists of 5 steps:

  1. IAM role creation (for Security Agent service principal)
  2. Agent Space creation
  3. Target Domain registration and verification (domain ownership proof)
  4. Associate VPC, IAM role, and Target Domain with Agent Space
  5. Pentest creation
IAM role and Agent Space creation
Terminal (IAM role)
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=ap-northeast-1
 
cat > securityagent-trust-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "securityagent.amazonaws.com" },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": { "aws:SourceAccount": "${ACCOUNT_ID}" }
    }
  }]
}
EOF
 
aws iam create-role \
  --role-name SecurityAgentPentestRole \
  --assume-role-policy-document file://securityagent-trust-policy.json
 
# VPC access policy (required for ENI creation)
aws iam attach-role-policy \
  --role-name SecurityAgentPentestRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonVPCFullAccess
 
# CloudWatch Logs policy (undocumented requirement)
cat > logs-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
    "Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/securityagent/*"
  }]
}
EOF
 
aws iam put-role-policy \
  --role-name SecurityAgentPentestRole \
  --policy-name SecurityAgentLogsAccess \
  --policy-document file://logs-policy.json
Terminal (Agent Space)
AGENT_SPACE_ID=$(aws securityagent create-agent-space \
  --name "pentest-verification" \
  --region $REGION \
  --query "agentSpaceId" --output text)
Target Domain registration and verification

You must register and verify ownership of test target domains. Two verification methods: DNS_TXT (add DNS record) and HTTP_ROUTE (place token at HTTP endpoint).

Terminal
TARGET_DOMAIN_ID=$(aws securityagent create-target-domain \
  --target-domain-name "$PRIVATE_DNS" \
  --verification-method HTTP_ROUTE \
  --region $REGION \
  --query "targetDomainId" --output text)

Important: the verify-target-domain CLI command performs HTTP_ROUTE verification from public internet. Private DNS names (*.compute.internal) return UNREACHABLE. However, when using VPC Config for pentesting, the PREFLIGHT step performs a separate VPC-internal domain verification which succeeds (logged as "Verifying ownership of private network domains → Completed"). In other words, CLI domain verification being UNREACHABLE does not block pentest execution via VPC Config.

Agent Space update and pentest creation
Terminal (Associate resources with Agent Space)
aws securityagent update-agent-space \
  --agent-space-id $AGENT_SPACE_ID \
  --name "pentest-verification" \
  --aws-resources "{
    \"vpcs\": [{
      \"vpcArn\": \"$VPC_ID\",
      \"securityGroupArns\": [\"$SG_ID\"],
      \"subnetArns\": [\"$SUBNET_ID\"]
    }],
    \"iamRoles\": [\"$SERVICE_ROLE_ARN\"]
  }" \
  --target-domain-ids "$TARGET_DOMAIN_ID" \
  --region $REGION
Terminal (Create pentest)
PENTEST_ID=$(aws securityagent create-pentest \
  --title "vulnerable-flask-app-pentest" \
  --agent-space-id $AGENT_SPACE_ID \
  --service-role $SERVICE_ROLE_ARN \
  --assets '{"endpoints": [{"uri": "http://'"$PRIVATE_DNS"'"}]}' \
  --vpc-config "{
    \"vpcArn\": \"$VPC_ID\",
    \"securityGroupArns\": [\"$SG_ID\"],
    \"subnetArns\": [\"$SUBNET_ID\"]
  }" \
  --region $REGION \
  --query "pentestId" --output text)

Note: specifying --code-remediation-strategy AUTOMATIC returns "Code remediation is not supported in ap-northeast-1". Automatic code fix generation is not available in Tokyo region (as of April 1, 2026).

Setup Results

StepDuration
IAM role creation + policy attachment~8s
Agent Space creation~3s
Target Domain registration~2s
Agent Space update (VPC, IAM, Target Domain)~3s
Pentest creation~2s
Total~18s

CLI-only setup completes in 18 seconds. Key constraints to note:

  • 1 Application per account (if already set up via console, no CLI creation needed)
  • Service role must be pre-registered in Agent Space's aws-resources.iamRoles
  • Service role needs CloudWatch Logs permission (logs:CreateLogGroup etc.) — undocumented
  • --code-remediation-strategy AUTOMATIC not available in ap-northeast-1

Verification 2: Penetration Test Execution and Finding Quality

Test Target

Deployed a Flask app with 5 intentional vulnerabilities on EC2 (t3.small). Since production applications are commonly placed in private subnets, we chose VPC Config-based private endpoint testing over public endpoints. Security Agent accesses via private DNS through VPC Config.

The 5 vulnerabilities intentionally mix "2 standard patterns detectable by traditional DAST tools" with "3 that require endpoint discovery or understanding of authentication context/business logic" — designed to measure how far Security Agent's "context-aware" approach extends beyond standard pattern matching.

#VulnerabilityCategoryExpected Detection Difficulty
1SQL Injection (login form)InjectionLow (standard pattern)
2Stored XSS (comment feature)XSSLow (standard pattern)
3IDOR (view other users' profiles)Access ControlMedium (auth context needed)
4JWT verification bypass (no signature check)AuthenticationMedium (business logic)
5Path Traversal (file download)InjectionMedium (standard pattern but no HTML links to endpoint)
Flask app source code (full app.py)
app.py
"""
Deliberately vulnerable Flask application for Security Agent penetration testing.
WARNING: Do NOT deploy in production environments.
"""
import os, sqlite3, hashlib, json, base64
from flask import Flask, request, jsonify, g, send_file
 
app = Flask(__name__)
app.config["SECRET_KEY"] = "hardcoded-secret-key-123"
DATABASE = "/tmp/vuln_app.db"
UPLOAD_DIR = "/tmp/uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
 
def get_db():
    if "db" not in g:
        g.db = sqlite3.connect(DATABASE)
        g.db.row_factory = sqlite3.Row
    return g.db
 
@app.teardown_appcontext
def close_db(exception):
    db = g.pop("db", None)
    if db is not None:
        db.close()
 
def init_db():
    db = sqlite3.connect(DATABASE)
    db.executescript("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            email TEXT, role TEXT DEFAULT 'user');
        CREATE TABLE IF NOT EXISTS comments (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER, content TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
    """)
    db.execute("INSERT OR IGNORE INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
               ("admin", hashlib.md5(b"admin123").hexdigest(), "admin@example.com", "admin"))
    db.execute("INSERT OR IGNORE INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
               ("alice", hashlib.md5(b"password1").hexdigest(), "alice@example.com", "user"))
    db.execute("INSERT OR IGNORE INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
               ("bob", hashlib.md5(b"password2").hexdigest(), "bob@example.com", "user"))
    db.commit()
    db.close()
 
# --- Vulnerability 1: SQL Injection ---
@app.route("/api/login", methods=["POST"])
def login():
    data = request.get_json()
    username, password = data.get("username", ""), data.get("password", "")
    password_hash = hashlib.md5(password.encode()).hexdigest()
    query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password_hash}'"
    try:
        user = get_db().execute(query).fetchone()
    except Exception as e:
        return jsonify({"error": str(e)}), 500
    if user:
        payload = base64.b64encode(json.dumps(
            {"user_id": user["id"], "username": user["username"], "role": user["role"]}
        ).encode()).decode()
        return jsonify({"token": f"header.{payload}.nosignature", "user_id": user["id"]})
    return jsonify({"error": "Invalid credentials"}), 401
 
# --- Vulnerability 2: Stored XSS ---
@app.route("/api/comments", methods=["GET"])
def get_comments():
    comments = get_db().execute(
        "SELECT c.id, c.content, c.created_at, u.username "
        "FROM comments c JOIN users u ON c.user_id = u.id ORDER BY c.created_at DESC"
    ).fetchall()
    return jsonify([dict(c) for c in comments])
 
@app.route("/api/comments", methods=["POST"])
def post_comment():
    token = request.headers.get("Authorization", "")
    user = decode_token(token)
    if not user:
        return jsonify({"error": "Unauthorized"}), 401
    content = request.get_json().get("content", "")
    get_db().execute("INSERT INTO comments (user_id, content) VALUES (?, ?)", (user["user_id"], content))
    get_db().commit()
    return jsonify({"message": "Comment posted"}), 201
 
# --- Vulnerability 3: IDOR ---
@app.route("/api/users/<int:user_id>", methods=["GET"])
def get_user_profile(user_id):
    token = request.headers.get("Authorization", "")
    user = decode_token(token)
    if not user:
        return jsonify({"error": "Unauthorized"}), 401
    profile = get_db().execute("SELECT id, username, email, role FROM users WHERE id = ?",
                               (user_id,)).fetchone()
    return jsonify(dict(profile)) if profile else (jsonify({"error": "Not found"}), 404)
 
# --- Vulnerability 4: JWT signature not verified ---
def decode_token(auth_header):
    try:
        token = auth_header.split("Bearer ")[1]
        payload = json.loads(base64.b64decode(token.split(".")[1] + "=="))
        return payload
    except Exception:
        return None
 
# --- Vulnerability 5: Path Traversal ---
@app.route("/api/files/download", methods=["GET"])
def download_file():
    filename = request.args.get("filename", "")
    filepath = os.path.join(UPLOAD_DIR, filename)
    return send_file(filepath) if os.path.exists(filepath) else (jsonify({"error": "Not found"}), 404)
 
@app.route("/health")
def health():
    return jsonify({"status": "ok"})
 
@app.route("/")
def index():
    comments = get_db().execute(
        "SELECT c.content, u.username FROM comments c JOIN users u ON c.user_id = u.id"
    ).fetchall()
    comments_html = "".join(f"<div class='comment'><b>{c['username']}</b>: {c['content']}</div>" for c in comments)
    return f"<!DOCTYPE html><html><head><title>Vulnerable App</title></head><body>"  \
           f"<h1>Comment Board</h1>{comments_html}</body></html>"
 
if __name__ == "__main__":
    init_db()
    app.run(host="0.0.0.0", port=80)
EC2 deployment steps

Uses the default VPC. Security Agent runs 13 attack categories in parallel, so Flask's development server (single-threaded) will crash under the load. gunicorn (multi-worker) is required.

Terminal (environment variables)
REGION=ap-northeast-1
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" \
  --query "Vpcs[0].VpcId" --output text --region $REGION)
VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids $VPC_ID \
  --query "Vpcs[0].CidrBlock" --output text --region $REGION)
SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \
  --query "Subnets[0].SubnetId" --output text --region $REGION)
AMI_ID=$(aws ssm get-parameters \
  --names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
  --query "Parameters[0].Value" --output text --region $REGION)
Terminal (IAM role and instance profile for EC2)
# Role for SSM connectivity
cat > ec2-trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}
EOF
 
aws iam create-role --role-name SecurityAgentPentestEC2Role \
  --assume-role-policy-document file://ec2-trust-policy.json
aws iam attach-role-policy --role-name SecurityAgentPentestEC2Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile \
  --instance-profile-name SecurityAgentPentestEC2Profile
aws iam add-role-to-instance-profile \
  --instance-profile-name SecurityAgentPentestEC2Profile \
  --role-name SecurityAgentPentestEC2Role
 
sleep 10  # Wait for IAM propagation
Terminal (security group and EC2 launch)
# SG: Allow HTTP 80 from within the VPC only
SG_ID=$(aws ec2 create-security-group \
  --group-name security-agent-pentest-target \
  --description "Pentest target - HTTP from VPC only" \
  --vpc-id $VPC_ID --region $REGION --query "GroupId" --output text)
 
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID --protocol tcp --port 80 \
  --cidr $VPC_CIDR --region $REGION
 
# Launch EC2 instance
INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID --instance-type t3.small \
  --subnet-id $SUBNET_ID --security-group-ids $SG_ID \
  --iam-instance-profile Name=SecurityAgentPentestEC2Profile \
  --associate-public-ip-address \
  --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=security-agent-pentest-target}]" \
  --region $REGION --query "Instances[0].InstanceId" --output text)
 
aws ec2 wait instance-running --instance-ids $INSTANCE_ID --region $REGION
 
PRIVATE_DNS=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID \
  --query "Reservations[0].Instances[0].PrivateDnsName" --output text --region $REGION)
echo "Private DNS: $PRIVATE_DNS"
Terminal (app deployment via SSM)
# Wait for SSM Agent to come online
while true; do
  STATUS=$(aws ssm describe-instance-information \
    --filters "Key=InstanceIds,Values=${INSTANCE_ID}" \
    --query "InstanceInformationList[0].PingStatus" --output text --region $REGION 2>/dev/null)
  [ "$STATUS" = "Online" ] && break
  echo "Waiting for SSM Agent..." && sleep 10
done
 
# Transfer app.py to EC2 via base64 encoding
APP_B64=$(base64 -w0 app.py)
aws ssm send-command --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters "{\"commands\":[
    \"sudo mkdir -p /opt/vulnerable-app\",
    \"echo '$APP_B64' | base64 -d | sudo tee /opt/vulnerable-app/app.py > /dev/null\"
  ]}" --region $REGION
 
# Install gunicorn + initialize DB + start the app
aws ssm send-command --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --timeout-seconds 120 \
  --parameters '{"commands":[
    "sudo python3 -m ensurepip --upgrade",
    "sudo python3 -m pip install flask==3.1.0 gunicorn",
    "cd /opt/vulnerable-app && sudo python3 -c \"exec(open('app.py').read().split('if __name__')[0]); init_db()\"",
    "sudo systemd-run --unit=vuln-app --working-directory=/opt/vulnerable-app gunicorn --bind 0.0.0.0:80 --workers 4 --timeout 120 app:app"
  ]}' --region $REGION
 
sleep 5
# Verify the app is running
aws ssm send-command --instance-ids $INSTANCE_ID \
  --document-name "AWS-RunShellScript" \
  --parameters '{"commands":["curl -s http://localhost/health"]}' \
  --region $REGION

Test Execution

Terminal
aws securityagent start-pentest-job \
  --pentest-id $PENTEST_ID \
  --agent-space-id $AGENT_SPACE_ID \
  --region $REGION

Monitor progress with batch-get-pentest-jobs. The test took approximately 2 hours 43 minutes to complete.

Terminal (check progress)
aws securityagent batch-get-pentest-jobs \
  --agent-space-id $AGENT_SPACE_ID \
  --pentest-job-ids <pentestJobId> \
  --region $REGION \
  --query "pentestJobs[0].{status:status,steps:steps[*].{name:name,status:status}}"

The test progresses through 4 steps:

  1. PREFLIGHT — Builds container environment inside the VPC, verifies endpoint reachability
  2. STATIC_ANALYSIS — Crawls endpoints, runs network and TLS scans
  3. PENTEST — Executes multi-step attack scenarios
  4. FINALIZING — Aggregates results, CVSS scoring, report generation

CloudWatch Logs (/aws/securityagent/<agent-space-name>/<pentest-id>) capture detailed execution logs. Observed internal tasks:

TaskDurationDescription
Setup Testing Environment~26 minBuilds compute, networking, and container inside VPC
Endpoint Accessibility ValidationsecondsHTTP connectivity check
SCANNER~7sNetwork scan (port/service discovery)
TLS SCANNER~8sTLS configuration scan
CRAWLER~29 minMultiple AI agents explore endpoints in parallel using browser and Python
PENTEST (attack tasks)~1h 37m13 attack categories in parallel + VALIDATOR TASK verification

The CRAWLER behavior is notable: multiple AI agents run in parallel, using browser sessions (browse tool) and Python requests to autonomously discover and analyze endpoints (at least 4 distinct agent_ids were observed in the first run's CloudWatch Logs). They probe unknown paths (/admin, /api/v1, /graphql), parse form structures, and map the application — a fundamentally different approach from traditional DAST crawlers.

During the PENTEST phase, 13 OWASP Top 10-centered attack categories (XSS, SQLi, IDOR, Path Traversal, SSRF, SSTI, Code Injection, Command Injection, LFI, XXE, JWT, Privilege Escalation, Arbitrary File Upload) ran in parallel. After each attack task completed, a VALIDATOR TASK reproduced findings to confirm confidence levels.

The first run failed because Flask's development server (single-threaded) crashed under parallel attack load. The second run used gunicorn with 4 workers and completed successfully. A multi-worker server configuration is required for pentest targets.

Results

After test completion, retrieve findings with list-findings and batch-get-findings.

Terminal (retrieve findings)
# List findings
aws securityagent list-findings \
  --agent-space-id $AGENT_SPACE_ID \
  --pentest-job-id <pentestJobId> \
  --region $REGION
 
# Get finding details (includes reproduction steps, CVSS, impact analysis)
aws securityagent batch-get-findings \
  --agent-space-id $AGENT_SPACE_ID \
  --finding-ids <findingId1> <findingId2> ... \
  --region $REGION

Total test duration was approximately 2 hours 43 minutes (PREFLIGHT ~32 min + STATIC_ANALYSIS ~29 min + PENTEST ~1h 37m + FINALIZING ~6 min).

VulnerabilityDetectedCVSSConfidenceNotes
SQL InjectionYes9.8 CRITICALHIGHAuth bypass reproduced with ' OR '1'='1. Also discovered credentials admin:admin123
Stored XSSYes6.4 MEDIUMLOWJS execution confirmed via <img onerror> payload. Also flagged missing CSP headers
IDORYes6.5 MEDIUMHIGHUsed alice's token to access GET /api/users/1 (admin) and retrieved profile data
JWT bypassYes10.0 CRITICALHIGHCrafted unsigned token with arbitrary user_id/role, reproduced privilege escalation
Path TraversalNo--Not detected
Unexpected: Integer OverflowYes4.3 MEDIUMHIGHGET /api/users/99999999999999999 triggered 500 error. Not a planted vulnerability

Detection rate: 4/5 (80%), false positives: 0

The reason Path Traversal went undetected cannot be confirmed definitively, but /api/files/download?filename= had no links on any HTML page and no API documentation or source code was provided, making it likely that the CRAWLER never discovered this endpoint. Registering source code as an artifact would expand the crawler's discovery scope, though whether that would lead to detection is unverified.

The unexpected Integer Overflow finding is worth noting — the agent discovered that extreme values for user_id cause a 500 Internal Server Error, an input validation issue that was not deliberately planted.

Finding quality is high. Each finding includes:

  • Detailed reproduction steps — in curl command format, copy-paste ready
  • CVSS scoring — 9.8 (SQL Injection), 10.0 (JWT) with appropriate severity ratings
  • Impact analysis — concrete descriptions of session hijacking, data leakage, etc.
  • Remediation guidance — specific fixes like parameterized queries and output escaping (within the finding description)

Cost

At $50/task-hour, the above duration (~2h 43m) comes to approximately $136 (per-second billing). The 2-month free trial for new customers (200 task-hours/month) easily covers tests of this scale.

Summary — Who Should Use This

In this verification, Security Agent detected 4 out of 5 known vulnerabilities in a small app (6 endpoints) with zero false positives. It is practical as continuous security testing that fills the gaps between periodic manual tests. The ability to detect business logic vulnerabilities (IDOR, JWT bypass) with HIGH confidence is a clear differentiator from traditional DAST tools.

AspectResultRating
CLI setup18s (IAM role to pentest creation)Excellent
PREFLIGHT (VPC env setup)~32 minGood
Total test duration~2h 43m (small app with 6 endpoints)Good
Detection rate4/5 (80%) + 1 unexpected findingExcellent
False positives0Excellent
Finding qualityReproduction steps, CVSS, impact analysis, remediation guidanceExcellent
Business logic vulnsIDOR and JWT bypass detected with HIGH confidenceExcellent
VPC testingSupported via VPC ConfigExcellent
Code remediationNot available in TokyoFair
Cost~$136 (2h 43m x $50/task-hour)Good
  • Best suited for filling gaps between periodic manual pentests — Manual pentesting (thousands to tens of thousands of dollars) typically happens 1-2 times per year. At ~$136/run, Security Agent enables testing at development-cycle frequency, with a 2-month free trial for evaluation
  • Register source code / API docs as artifacts for potential detection improvement — We achieved 4/5 detection with URL-only configuration. The missed Path Traversal was on an endpoint with no HTML links. Registering source code could expand the crawler's discovery scope (unverified)
  • Factor in ~32 min PREFLIGHT overhead for CI/CD — VPC environment setup takes time, making nightly batches or weekly schedules more practical than pre-deploy gates
  • Use multi-worker servers for test targets — 13 attack categories run in parallel; single-threaded servers can't handle the load. Production environments are typically fine, but staging/test environments may need gunicorn or equivalent

Production Considerations

  1. Domain verification with VPC Config — The CLI verify-target-domain returns UNREACHABLE for private DNS, but VPC Config pentests perform internal VPC verification during PREFLIGHT, so test execution is not blocked. Target Domain registration is still required
  2. Don't forget CloudWatch Logs permissions — Without logs:CreateLogGroup on the service role, PREFLIGHT fails immediately. Not documented, easy to miss when creating IAM policies
  3. Code Remediation not available in Tokyo--code-remediation-strategy AUTOMATIC is not available in Tokyo (supported regions unconfirmed). Choose region accordingly if code fix suggestions are needed

Cleanup

Resource deletion
Terminal
# Delete pentest
aws securityagent batch-delete-pentests \
  --agent-space-id $AGENT_SPACE_ID \
  --pentest-ids $PENTEST_ID --region $REGION
 
# Delete target domain
aws securityagent delete-target-domain \
  --target-domain-id $TARGET_DOMAIN_ID --region $REGION
 
# Delete agent space
aws securityagent delete-agent-space \
  --agent-space-id $AGENT_SPACE_ID --region $REGION
 
# Terminate EC2
aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $REGION
 
# Delete IAM roles
aws iam detach-role-policy --role-name SecurityAgentPentestRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonVPCFullAccess
aws iam delete-role-policy --role-name SecurityAgentPentestRole \
  --policy-name SecurityAgentLogsAccess 2>/dev/null
aws iam delete-role --role-name SecurityAgentPentestRole
 
aws iam detach-role-policy --role-name SecurityAgentPentestEC2Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam delete-instance-profile \
  --instance-profile-name SecurityAgentPentestEC2Profile
aws iam delete-role --role-name SecurityAgentPentestEC2Role

Share this post

Shinya Tahara

Shinya Tahara

Solutions Architect @ AWS

I'm a Solutions Architect at AWS, providing technical guidance primarily to financial industry customers. I share learnings about cloud architecture and AI/ML on this site.The views and opinions expressed on this site are my own and do not represent the official positions of my employer.

Related Posts