Skip to content

feat: Go port of SEC-AF — installing this repo now gives you the Go node - #130

Merged
AbirAbbas merged 5 commits into
mainfrom
feat/go-port
Aug 20, 2026
Merged

feat: Go port of SEC-AF — installing this repo now gives you the Go node#130
AbirAbbas merged 5 commits into
mainfrom
feat/go-port

Conversation

@AbirAbbas

Copy link
Copy Markdown
Contributor

Summary

Complete 1:1 port of SEC-AF from Python to Go on the AgentField Go SDK, following the porting pattern established by pr-af (Agent-Field/pr-af#53, #54, #64) and SWE-AF (Agent-Field/SWE-AF#94). The Python implementation is byte-untouched — every diff lives under go/, plus docker-compose.go.yml, one root-README section, the root-manifest redirect, and a new Go CI workflow.

Like pr-af's end state, the Go node takes the product's name: the root manifest declares superseded_by: …//go, so af install https://github.com/Agent-Field/sec-af installs the Go package as sec-af (default port 8013) and replaces an existing Python install in place — same node id, same triggers, node-scoped secrets kept. Installing a cloned checkout as a local path remains the documented Python escape hatch. docker-compose.go.yml is an add-on that runs the Go node as sec-af-go beside the Python stack during a changeover.

Parity contract honored

  • Registration surface: all 34 reasoners under their exact Python names — audit plus the 33 router reasoners tagged ["security","audit","red-team"] — each publishing the same input schema the Python SDK derives from the function signature (captured from a live Python node into a committed fixture; a parity test reads them back through /discover).
  • Same DAG: every place Python does app.call(f"{NODE_ID}.x", …), Go does a tracked agent.Call with identical targets and kwargs — audit → recon/hunt/prove/remediation_phase → mappers / hunters+dedup / verifiers / remediation, same gather shapes and semaphore bounds, so the control plane draws the identical workflow DAG.
  • Result JSON: pydantic model_dump() key sets and defaults reproduced (default-seeding UnmarshalJSON, exclude_none at the same call sites, pydantic-isoformat timestamps, uuid4 id constructors).
  • Prompts byte-verbatim: all 30 templates embedded with a drift test against the Python tree; every prompt builder golden-tested against the Python implementation (generators committed under go/scripts/).
  • Python quirks reproduced, not fixed (each commented at the source): the compliance-report arity TypeError, the inert enable_dast parameter, the dast-verifier arity mismatch, _repo_metrics' skip-dir behavior, the hunter TypeError-cascade's effective signatures, the split 400/500 error mapping (only the checkpoint ValueError is inside Python's try), and both _default_strategies variants.
  • .ai() parity: the structured-output path reproduces the Python SDK's tolerant parsing (direct parse → greedy {.*} salvage → up to 2 request retries → Could not parse structured response: …) — found by live testing when a model wrapped its JSON in a markdown fence.

Verification

  • go build ./… && go vet ./… && go test ./… && go test -race ./… green; gofmt -l empty; ~700 Go test functions, all derived from the Python tests/behavior (goldens + fixtures generated from the live Python models by committed scripts). Also run on the CI-pinned go1.21 toolchain.
  • Live DAG parity against a real control plane: both nodes registered on one isolated control plane with a deterministic mock opencode harness; the same audit (depth standard) triggered through the async execute API on a seeded vulnerable fixture repo.
    • AI-off (fully deterministic): the Go and Python DAGs are strictly identical — same 44 executions, same 23-edge multiset, same statuses — and the final audit payloads are identical after masking the embedded AI-transport error string (litellm vs Go SDK text) and the completion-ordered demotion list (nondeterministic in Python run-to-run as well).
    • AI-on (real OpenRouter .ai() gates): edge multisets identical; only the remediation_phase→run_remediation fan-out moves, because run_verdict_agent is a live LLM call whose verdicts decide remediation — the same nondeterminism the Python node has.
  • Real-harness proof: run_architecture_mapper executed on the Go node with the real opencode CLI and openrouter/moonshotai/kimi-k2.5, returning a schema-valid ArchitectureMap of the fixture repo.
  • Both Docker images build (docker build -f go/Dockerfile . from the repo root; aforge fetch checksum-verified); image smoke-tested (non-root user, entrypoint generates opencode.json from HARNESS_MODEL).
  • Python tree untouched: git diff origin/main -- src tests pyproject.toml Dockerfile docker-compose.yml is empty.

Known accepted differences (documented in code)

  • The SDK's built-in /health body is {"status":"ok"} vs Python's {"status":"healthy",…} — every consumer only checks the status code.
  • AGENT_CALLBACK_URL unset: Go lets the SDK derive localhost:<actual port> instead of reproducing Python's fallback that advertises a port the node may not listen on.
  • Where Python's own output is unstable across processes (set iteration, dict completion order), the Go port is deterministic and commented.
  • Two stale Python test assertions (reachability default in test_scoring.py) contradict the current scoring.py; the port pins the code's interpreter-verified behavior and flags the tests.

🤖 Generated with Claude Code

AbirAbbas and others added 5 commits August 20, 2026 02:10
The shared substrate every ported package builds on: afx (map<->struct
binding, the _unwrap/_as_dict envelope helpers, exclude_none), pyfmt
(Python round/str/repr/json.dumps parity), appx (the Harness/AI/Note/Call
agent seam + recording test fake), harnessx (generic structured-harness
runner resolving committed pydantic schema fixtures by Go type name),
aix (the .ai(schema=) path with the Python SDK's strictify, salvage and
parse-retry semantics), byte-verbatim embedded prompts with a drift test
against the Python tree, config (env parity incl. precedence quirks),
schemas (every pydantic model with default-seeding UnmarshalJSON,
pydantic-isoformat timestamps, uuid4 constructors) and scoring.

Fixtures and goldens are generated from the live Python models by the
committed scripts under go/scripts and pinned by parity tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
recon mappers (+ deterministic _parsers port and repo metrics), the
scan->enrich hunter pipeline for all 12 strategies, fingerprint+AI dedup
and chain correlation, the prove sub-agents (tracer, sanitization,
exploit, verdict via .ai(), assembler, chain builder, dast, cross-service,
dep reachability, sandbox), remediation, recon-context pruning with the
language/framework hint tables, the AI gates with transient-error retry,
the static compliance mapping with the hybrid AI fallback and gap report,
the SARIF/JSON/Markdown/compliance-report emitters, diff analysis,
monitoring baselines and policy evaluation.

Prompt builders are golden-tested byte-for-byte against the Python
implementations; output artifacts are golden-tested against fixtures
generated by the committed gen_golden scripts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
internal/phases carries recon/hunt/prove/remediation_phase — the
reasoners the audit drives through tracked app.Call invocations with the
exact Python targets, kwargs, semaphore bounds and notes, so the
control-plane DAG is identical to the Python node's. internal/orch ports
AuditOrchestrator: checkpoints, budget/cost bookkeeping, the phase
harness proxy, reachability assessment, drop tracking and
generate_output (severity floors, exploitability scores, hybrid
compliance, SARIF/report generation), including the documented Python
bugs reproduced bug-for-bug (compliance-report arity, inert enable_dast,
dast-verifier arity).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
internal/reasoners registers the audit reasoner plus the 33 router
reasoners under their exact Python names, tags and signature-derived
input schemas (captured from a live Python node and embedded as the
parity fixture). internal/node ports app.py: agent construction from the
environment (AGENTFIELD_URL precedence, callback URL, harness + AI
config), the audit handler with its four phase calls, checkpointing and
error mapping, and the repo resolver (clone/pull semantics). cmd/sec-af
is the entry point, default node id sec-af on :8013.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sec-af

The root manifest gains superseded_by pointing at go/, whose manifest
declares the same name and node id, so a git install lands the Go node
and replaces an existing Python install in place (same id, same
triggers, secrets kept); a local-path install remains the documented
Python escape hatch. Adds the multi-stage Go Dockerfile (checksum-
verified AForge fetch + opencode, non-root user), the model-aware
entrypoint, a compose add-on that joins the Python stack under the
sec-af-go id, Makefile, go/README, a root-README section and a Go CI
workflow (build/vet/test/gofmt + docker build).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas merged commit 6c8eef6 into main Aug 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant