> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗ whois is redacted, cdns mask the rest — get the real operator — try whoisgeni.us ↗ who really runs that domain ↗ domain attribution that shows its work — full evidence chain — try whoisgeni.us ↗ domain intel w/ evidence ↗

← Back to Articles

Ares: When LLMs Run Your Penetration Tests (And Fight Themselves)

[ View on GitHub ]

Ares: When LLMs Run Your Penetration Tests (And Fight Themselves)

Hook

Most LLM security frameworks fail because prompt injection lets attackers control shell commands. Ares inverts this: the LLM never touches execution primitives—it just decides which specialized worker runs next.

Context

Traditional adversary emulation platforms like Caldera or Cobalt Strike require security operators to manually chain attack sequences: enumerate the domain, crack credentials, move laterally, escalate privileges. Each step needs human decision-making based on discovered state. You run BloodHound, analyze the graph, pick an attack path, then execute tools like secretsdump or Rubeus one at a time. This works but doesn't scale—a single purple team exercise against a complex Active Directory environment can consume weeks of operator time.

LLM-based automation seemed like the obvious solution, but early attempts hit a fatal flaw: if your LLM orchestrator directly executes shell commands based on natural language reasoning, prompt injection becomes a critical vulnerability. An attacker who controls any text the LLM ingests—log files, DNS responses, error messages—can inject commands. Ares solves this by implementing a strict architectural boundary: the LLM orchestrator only dispatches typed tasks to Redis queues. Specialized workers pull these tasks and execute domain-specific binaries like nmap, hashcat, or impacket tools. The LLM reasons about attack graphs and sequences operations, but never constructs shell commands. This separation makes Ares the first autonomous offensive security platform where you can actually run it against semi-trusted infrastructure without handing prompt injection full system access.

Technical Insight

--k8s/--ec2

Query State

Reasoning

Attack Decisions

Push Tasks

Pull Tasks

Pull Tasks

Tool Results

nmap/hashcat/BloodHound

IOC Findings

Grafana/Loki queries

Historical

Ares CLI

Single Binary

Transport Layer

kubectl/SSM

LLM Orchestrator

Planning Loop

Redis

Task Queue

LLM API

OpenAI/Anthropic/Local

Red Team Workers

recon/cracker/privesc/lateral

Blue Team Workers

triage/threat_hunter/analyst

PostgreSQL

Query Store

System architecture — auto-generated

The architecture centers on a Redis-backed task queue with seven role-specific red team workers and four blue team investigation agents, all coordinated by a central LLM orchestrator. What makes this elegant is the transport abstraction—the entire system compiles to a single Rust binary with subcommands that work locally or remotely via --k8s or --ec2 flags. No separate deployment artifacts, no SSH bastions, no kubectl manifest hell. The CLI itself handles AWS SSM Session Manager tunneling or kubectl exec transport, so ares orchestrator --ec2 MyInstance just works.

The orchestrator runs a loop that queries discovered state from Redis, feeds it to an LLM (OpenAI, Anthropic, or local models via llama.cpp), and dispatches tasks based on the model's reasoning. Here's the critical safety pattern from the codebase:

// Orchestrator never executes commands directly
match llm_decision {
    AttackDecision::EnumerateTargets { subnet } => {
        redis_client.rpush(
            "tasks:recon",
            serde_json::to_string(&ReconTask {
                task_type: TaskType::NetworkScan,
                target: subnet,
            })?
        )?;
    },
    AttackDecision::CrackHashes { hash_list } => {
        redis_client.rpush(
            "tasks:cracker",
            serde_json::to_string(&CrackerTask {
                task_type: TaskType::Hashcat,
                hashes: hash_list,
                wordlist: "rockyou.txt",
            })?
        )?;
    },
    // LLM outputs are deserialized into typed enums,
    // never passed to shell interpreters
}

Workers pull tasks, execute hardcoded tool invocations, and push structured results back. The credential_access worker runs impacket's secretsdump, parses NTLM hashes, and stores them in Redis with metadata about source machines. The cracker worker feeds these to hashcat, then notifies the orchestrator when plaintexts are discovered. The ACL worker is particularly sophisticated—it queries BloodHound's Neo4j database for privilege escalation paths, ranks them by edge count and required primitives, then auto-dispatches lateral or privesc workers to execute techniques like shadow credentials or WriteDACL abuse.

The automation modules are where operator toil disappears. Fourteen concurrent tasks monitor Redis state and trigger attack sequences:

// Simplified automation module pattern
async fn credential_spray_automation(redis: &RedisClient) {
    loop {
        let usernames = redis.smembers("discovered:users")?;
        let passwords = redis.smembers("cracked:passwords")?;
        
        if !passwords.is_empty() {
            for password in passwords {
                redis.rpush("tasks:credential_access", 
                    CredentialSprayTask {
                        users: usernames.clone(),
                        password,
                        protocol: Protocol::LDAP,
                    }
                )?;
            }
        }
        tokio::time::sleep(Duration::from_secs(30)).await;
    }
}

When hashcat cracks a password, the automation module immediately sprays it against all discovered user accounts. Valid credentials trigger lateral movement tasks. This is event-driven offensive automation—no manual Cobalt Strike beacon management.

Blue team mode flips the model entirely. Instead of executing attacks, agents query observability platforms (Grafana, Loki, Prometheus) for evidence. The triage agent pulls recent alerts, the threat_hunter runs queries for specific IOC patterns, and the lateral_analyst correlates authentication events across hosts. The orchestrator chains investigation tasks based on what surfaces—if lateral movement is detected, it auto-dispatches escalation triage to check for privilege changes. The benchmark replay workflow captures these Loki timelines to S3, then you can provision ephemeral infrastructure stacks and replay the exact attack sequence to test detection rule changes. You iterate on SIEM queries without re-running expensive 6-hour red team operations.

The EC2 deployment workflow cross-compiles the Rust binary to x86_64 Linux, uploads to S3, then uses AWS Systems Manager to pull and execute on target instances. No agents to install, no Terraform state to manage for worker infrastructure—just compile, upload, run. The acknowledged Rosetta emulation requirement on Apple Silicon is a practical tradeoff; the alternative (QEMU-based cross-compilation) segfaults on macOS due to Docker Desktop's VM architecture. They chose developer ergonomics over perfect portability.

Gotcha

Redis as the primary operational state store is the Achilles' heel. All discovered credentials, task queues, and attack state live in memory. If the Redis instance crashes mid-operation, you lose everything since the last manual checkpoint. There's no write-ahead log, no replication topology mentioned in the docs, and PostgreSQL only stores historical data for post-operation analysis, not live state. For a 6-hour automated penetration test that discovered 40 compromised accounts and privilege escalation paths, a Redis restart means starting over.

Authentication and multi-tenancy don't exist. Anyone with network access to Redis can read all discovered credentials, inject malicious tasks, or manipulate the orchestrator's state. The documentation explicitly positions this as lab-only infrastructure, which is honest but limiting. You can't run this as a shared service for multiple security teams, can't implement approval workflows for sensitive operations, and can't isolate red team exercises from each other. Tool execution in workers also lacks sandboxing—they run with full system privileges, so a compromised worker (via a malicious LLM output that somehow bypasses type safety, or a vulnerability in impacket/hashcat) has unrestricted access. No seccomp filters, no user namespaces, no capability dropping. The LLM cost controls are also primitive; there's tracking but no budget limits, so a runaway orchestrator loop with expensive models could burn thousands in API calls during a long autonomous operation.

Verdict

Use if: You're a red team or purple team operator running controlled adversary simulations in isolated lab environments, you want reproducible multi-stage Active Directory attacks without manual tool sequencing, or you're evaluating defensive tooling and need realistic attack chains with built-in detection correlation. The automation modules and LLM coordination genuinely reduce operator toil compared to manual Cobalt Strike workflows, and the benchmark replay feature enables continuous security validation without re-running expensive operations. Skip if: You need production-grade multi-tenancy, any form of access control or audit compliance (SOC-2, FedRAMP), plan to run this on infrastructure you don't fully control, or want a general-purpose LLM agent framework. The lack of sandboxing, fragile Redis state persistence, and zero authentication make this suitable only for throwaway lab infrastructure. Also skip if you're not deeply familiar with Active Directory attack primitives—this automates sophisticated techniques but assumes you understand what shadow credentials and WriteDACL attacks actually do.