Inspiration

Security audits are broken — not because the tools are bad, but because the gap between what you want to know and how to actually get that answer is enormous. Nmap, Lynis, Nuclei, FFUF — these are powerful in the right hands, but stringing them together into a coherent audit requires deep expertise that most teams just don't have. A developer can't simply decide to run a HIPAA compliance check and know which Lynis flags map to which controls, what to do with the raw output, or how to write a remediation plan from it.

I wanted to flip that. What if you could just say what you wanted checked, and a capable agent figured out the rest?

The dual-mode idea came from realizing the gap exists on both sides of the fence — internal compliance teams struggling to keep up with frameworks like NIST and GDPR, and security researchers doing bug bounty work who need to chain five different tools in the right order to find something worth reporting. GoSec-ADK tries to solve both.


What I Learned

Building this forced me to get serious about a few things I'd only touched on before.

Agentic patterns in practice. The ReAct loop (Reason → Act → Observe) sounds clean in a paper, but implementing it in a way that actually holds — where the agent doesn't get stuck in tool call cycles or lose context from earlier in the session — required a lot of iteration. The knowledge graph was the real solution: every finding gets ingested centrally so the agent always has the full picture before deciding what to do next.

Security tooling at depth. Building wrappers that normalize raw CLI output into something an LLM can reason about taught me more about Nmap, Nuclei, and Lynis than years of casual use. Understanding what these tools are actually checking under the hood is different from knowing how to run them.

Model-agnostic design. Supporting Gemini, GPT-4o, Anthropic, and local models via OpenAI-compatible APIs (Ollama, Nvidia NIM) without duplicating logic everywhere forced careful thinking about abstraction boundaries in Go. The provider interface ended up cleaner than I expected once I stopped trying to accommodate every model's quirks individually.

Compliance as code. Translating HIPAA, NIST, CIS, and GDPR into YAML profiles that an agent can reason about is a genuinely interesting design problem. A compliance standard isn't just a checklist — it's a set of controls with relationships, severities, and context. Flattening that into machine-readable form without losing the nuance took several rewrites.


How I Built It

GoSec-ADK is written in Go, structured around the Google Agent Development Kit (ADK) pattern. The core is a ReAct-loop agent that takes a natural language instruction, decides which tool to invoke, in what order, and what to do with the output.

The architecture has four main layers:

  • Tool wrappers (pkg/wrappers/) — Go wrappers around CLI security tools that capture, parse, and normalize output into structured data the agent can work with
  • Knowledge graph — an in-memory graph where all findings land, linked by relationships: root domain → subdomain → endpoint → vulnerability
  • YAML compliance profiles (profiles/) — machine-readable definitions of security standards consumed by the compliance-mode agent
  • LLM provider abstraction — a single interface the agent uses to call any supported model, keeping provider logic isolated

The dual-mode split (compliance vs. offensive) came from recognizing that the two workflows have fundamentally different tool chains and output expectations. Compliance mode matches findings against controls. Offensive mode chains recon → probing → exploitation → automated PoC generation. One linear flow would've made both worse.

Attack path detection in the knowledge graph is a directed graph traversal problem. If we define the asset graph as $G = (V, E)$, where vertices $V$ represent assets (domains, endpoints, hosts) and edges $E$ represent discovered relationships or confirmed vulnerability links, then a valid attack path from external entry point $s$ to sensitive asset $t$ is any path $P = (v_0, v_1, \ldots, v_k)$ such that:

$$v_0 = s, \quad v_k = t, \quad \forall\, i: (v_i,\, v_{i+1}) \in E$$

The agent automatically surfaces these paths after the scan phase rather than leaving the analyst to trace them manually through raw output.


Challenges

Parsing raw CLI output reliably. Tools like Nmap and FFUF produce output optimized for humans, not machines. Getting Go wrappers to parse Nmap XML reliably across different scan types, or extract structured findings from FFUF responses, was more work than it looks. Edge cases everywhere — timeouts, partial output, unexpected flag combinations.

Preventing tool call loops. Early versions of the agent would sometimes cycle — running the same tool twice, or invoking a follow-up scan without enough context to act on it. Fixing this meant tightening the system prompts and giving the agent explicit stopping conditions tied to the state of the knowledge graph.

Knowledge graph schema design. The graph needs to be expressive enough to represent multi-hop attack paths but simple enough to query fast during a live session. The first few schemas were either too rigid (couldn't represent unexpected relationship types) or too loose (queries were slow and imprecise). The current version is a compromise that works well in practice.

Making compliance profiles actually useful. A YAML file that says "check SSH root login" is easy. One that accurately maps HIPAA technical safeguards or NIST SP 800-53 controls to concrete, executable checks — without turning into a maintenance nightmare — is genuinely hard. The current approach works, but there's a lot of room to grow here.

Built With

Share this project:

Updates

Submission history