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 expertise 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 turn that into a remediation plan.

At the same time, security researchers doing bug bounty work face the opposite problem: too many tools, too much context to hold in your head, and no clean way to chain recon into probing into exploitation into a submission-ready report.

I wanted to flip both of those. What if you could just say what you wanted checked — and a capable agent handled the rest?


What it does

GoSec-ADK is a model-agnostic AI security agent built in Go on the Google Agent Development Kit. It takes natural language instructions and turns them into real security work — no configuration files, no manual tool chaining, no post-processing of raw output.

It runs in two modes:

Compliance mode — the agent maps your environment against security standards (HIPAA, NIST SP 800-53, GDPR, CIS, PCI-DSS) by orchestrating Nmap, Lynis, and Gitleaks, then generates a structured audit report with findings and remediation steps. Standards are defined as YAML profiles, so they're readable, versioned, and extensible.

Offensive mode — the agent runs a full attack surface workflow: subdomain enumeration with Subfinder, HTTP probing with Httpx, crawling with Katana, vulnerability scanning with Nuclei, fuzzing with FFUF, and automated PoC generation in a format ready for HackerOne submissions.

Both modes feed into a central knowledge graph. Every finding — a subdomain, an open port, a vulnerable endpoint — is ingested as a node with typed edges. The agent queries this graph to reason about what it's found and decide what to do next. Attack path detection is then a directed graph traversal problem: given the graph $G = (V, E)$ where vertices $V$ are assets and edges $E$ are discovered relationships or confirmed vulnerability links, a valid attack path from 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 surfaces these paths automatically instead of leaving the analyst to trace them manually through a wall of raw output.

GoSec supports Gemini, GPT-4o, Anthropic's models, and any local model served via an OpenAI-compatible API (Ollama, Nvidia NIM).


How we built it

GoSec-ADK is written in Go, structured around the Google ADK's ReAct loop — Reason, Act, Observe. The agent receives an instruction, decides which tool to invoke and in what order, observes the output, updates its internal state, and loops until the task is done or it determines it has enough to generate a report.

The architecture has four main layers:

Tool wrappers (pkg/wrappers/) — thin Go wrappers around CLI security tools. Each wrapper runs the binary, captures output, and normalizes it into a typed struct the agent can reason about. The hardest part here wasn't writing the wrappers — it was handling the chaos: partial output, unexpected exit codes, scan types that change the output format, timeouts on slow targets.

Knowledge graph — an in-memory directed graph where every finding lands. Nodes are typed (Domain, Subdomain, Host, Endpoint, Vulnerability, Service) and edges carry relationship metadata. The agent reads from and writes to this graph on every tool call, so it always has the full picture of what it knows before deciding what to do next.

YAML compliance profiles (profiles/) — machine-readable definitions of security standards. Each profile maps a framework's controls to specific tool checks, expected states, severity levels, and remediation guidance. The agent loads the relevant profile at the start of a compliance session and uses it to interpret tool output in context.

LLM provider abstraction — a single Provider interface with implementations for each supported model. All provider-specific logic (API format, context window management, tool call formatting) lives in its own package, keeping the agent core clean and model-agnostic.

The dual-mode split was a deliberate architectural decision. Compliance and offensive work have different tool chains, different output expectations, and different definitions of "done." Cramming both into one unified flow would have made both worse.


Challenges we ran into

Parsing raw CLI output reliably. Security tools produce output designed for humans, not for machines. Nmap XML is reasonable until you hit scan types that don't produce it. FFUF output varies depending on which flags you pass. Gitleaks changes its format between versions. Getting wrappers stable enough that the agent could trust what they returned took far longer than expected.

Preventing tool call loops. Early builds would sometimes cycle — running the same scan twice, or calling a follow-up tool without having enough context to act on the result. The fix was a combination of tighter system prompts, explicit stopping conditions tied to knowledge graph state, and giving the agent a way to signal "I have enough information to generate the report" rather than always looking for more.

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 (unexpected relationship types couldn't be represented) or too loose (traversal queries were slow and returned too much noise). The current version is a pragmatic middle ground.

Making compliance profiles accurate without making them unmaintainable. A YAML file that says "check SSH root login" is easy. One that accurately represents the control hierarchy of NIST SP 800-53 — with proper severity levels, control mappings, and enough context for the agent to write a useful remediation — is a different problem. The current profiles work, but getting the balance right took several complete rewrites.

Model-agnostic tool call formatting. Different models handle tool use differently. What works perfectly with GPT-4o breaks with a local model that doesn't support the same function-calling schema. The provider abstraction helped, but edge cases — especially with smaller local models — required per-provider prompt engineering that the abstraction couldn't fully hide.


Accomplishments that we're proud of

The knowledge graph actually works the way it was supposed to. Early on it felt like over-engineering — why not just pass tool output directly to the agent? But once the offensive mode was handling five tools in sequence, the graph became load-bearing. The agent could look at what it already knew about a target and make genuinely better decisions about what to scan next. That emergent behavior wasn't planned; it just happened because the architecture was right.

The dual-mode design staying clean through development. It would have been easy to let compliance and offensive bleed into each other as the codebase grew. They didn't. The shared core (agent loop, knowledge graph, provider abstraction) is genuinely shared, and the mode-specific code is genuinely separate.

Getting the PoC generator to produce submissions that look like something a real researcher would write, not a tool's templated output.


What we learned

Agentic patterns are harder to debug than regular code. When the agent makes a wrong decision, the failure mode isn't a stack trace — it's a plausible-looking output that's quietly wrong. Building good observability into the agent loop (logging every Reason-Act-Observe cycle in a way you can actually read) matters more than most infrastructure work.

Security tooling at depth. Using a tool and wrapping a tool are different things. Building the wrappers forced a much more thorough understanding of what these tools are actually checking, how they fail, and what their output means in edge cases. That knowledge fed back into the prompts and the profile design in ways that wouldn't have happened otherwise.

Go's interface system is well-suited for this kind of abstraction. The provider interface and the tool wrapper interface both stayed stable throughout development despite significant internal changes. That stability made it easy to add new providers and new tools without touching the agent core.

Compliance as code is undersolved. The YAML profile approach works, but the tooling around it barely exists. Validating a profile against a real standard, testing that a profile's checks actually catch the things the control requires, diffing profiles across standard versions — none of that is easy to do today. That problem space is bigger than GoSec.


What's next for GoSec

Persistent knowledge graph. Right now the graph is in-memory and dies when the session ends. Persisting it across sessions — with a proper graph database backend — would let the agent build up a picture of a target over time and run incremental scans rather than starting from scratch every time.

Authenticated scanning. The current tool suite is limited to unauthenticated external reconnaissance and local compliance checks. Adding support for authenticated web app scanning (Burp Suite integration, cookie/JWT injection) would make GoSec genuinely useful for deeper application security work.

Richer profile tooling. A profile editor, a validator that checks profiles against the published standard documents, and a community repository of profiles for additional frameworks (SOC 2, ISO 27001, FedRAMP).

CI/CD integration. A GitHub Action and a GitLab CI component that can run a lightweight compliance scan on pull requests and fail the build when critical controls regress.

Collaborative sessions. Multiple analysts working against the same knowledge graph simultaneously, with the agent coordinating across them.

Built With

  • agentsdk
Share this project:

Updates

Submission history