Documentation
¶
Overview ¶
Package audit is the security audit's entry point inside codeaf: the request, the repository it reads, the four-phase pipeline, and the in-process Caller that answers the pipeline's `.call`s.
It replaces the sec-af node's wiring layer (its `audit` reasoner, its repository resolution and its control-plane registration). What the node did over HTTP — route `sec-af.recon_phase` to a reasoner of the same node, which in turn called `sec-af.run_architecture_mapper` and the rest — happens here in process: Run wraps the App with WithLocalCalls, whose registry holds the same 33 reasoners under the same names.
Index ¶
- Variables
- func IsBadInput(err error) bool
- func ResolveRepo(repoURL string) (string, error)
- func Run(ctx context.Context, app appx.App, req AuditRequest) (schemas.SecurityAuditResult, error)
- func RunInput(ctx context.Context, app appx.App, input map[string]any) (schemas.SecurityAuditResult, error)
- func WithLocalCalls(inner appx.App) appx.App
- type AuditRequest
- type CallError
- type Error
Constants ¶
This section is empty.
Variables ¶
var ErrBadInput = errors.New("bad input")
ErrBadInput marks a ValueError-class failure raised INSIDE the pipeline, i.e. one Python maps to
except ValueError as exc: raise HTTPException(400, detail={"error": str(exc)})
Wrap an error with %w against this sentinel to have IsBadInput classify it.
var ErrRemoteRepository = errors.New("the security audit reads a local directory and does not clone repositories")
ErrRemoteRepository is the refusal for a repository given as a URL. Inside codeaf the audit reads a checkout that is already on disk; it never clones.
Functions ¶
func IsBadInput ¶
IsBadInput reports whether err is one of the ValueError-class failures the audit pipeline can raise from inside the mapped region.
THREE families reach it, all of them ValueError subclasses in Python:
- `ValueError(f"Unknown checkpoint phase: {phase}")` from orchestrator.run_from_checkpoint -> *orch.UnknownCheckpointPhaseError.
- `Model.model_validate(payload)` / `Model(**payload)` anywhere inside the try — app.py:181 ReconResult, :191 HuntResult, :203 and :217 VerifiedFinding, plus orchestrator.py's `_read_checkpoint`. pydantic's ValidationError SUBCLASSES ValueError, so EVERY schema failure inside the try takes the 400 branch, not the 500 one -> *phases.ValidationError.
- `json.loads` on a corrupt checkpoint file: `json.JSONDecodeError` is also a ValueError -> encoding/json's *SyntaxError / *UnmarshalTypeError, which are what orch.ReadCheckpoint / ReadCheckpointList and afx.Bind return for the same input.
A failure INSIDE a phase reasoner does not reach this classification with its own type: the in-process Call reports it as an opaque *CallError, exactly as the control-plane hop reported a failed child execution, so it is a 500.
func ResolveRepo ¶
ResolveRepo turns the request's repo_url into the absolute, symlink-free directory the audit reads.
It replaces `_resolve_repo(repo_url)` (src/sec_af/app.py:75), and is deliberately narrower. The node cloned a URL into a workspaces directory (SEC_AF_WORKSPACES_DIR, falling back to ~/.sec-af/workspaces), pulled an existing clone, and silently audited SEC_AF_REPO_PATH or its own working directory when repo_url was neither a URL nor a directory. Inside codeaf all three would be surprises — a network fetch nobody asked for, a directory written outside the project, or an audit of the wrong tree — so only an existing local directory is accepted, and everything else is refused with a sentence that says why.
func Run ¶
func Run(ctx context.Context, app appx.App, req AuditRequest) (schemas.SecurityAuditResult, error)
Run audits one local repository and returns the audit result, running every `.call` in the pipeline in process (WithLocalCalls). app supplies Harness, AI and Note; its own Call is never used.
A failure is always an *Error.
func RunInput ¶
func RunInput(ctx context.Context, app appx.App, input map[string]any) (schemas.SecurityAuditResult, error)
RunInput is Run for an untyped keyword map — the body the node's `audit` reasoner received — validated and bound by BindRequest first.
func WithLocalCalls ¶
WithLocalCalls wraps inner so that every Call resolves to the in-process reasoner registry, and returns the wrapper.
The registry is built over the WRAPPER, not over inner: the `*_phase` handlers make their nested `.call`s through the App they were registered with, and registering them with the wrapper is what keeps those nested calls in process too. inner's own Call is never used.
Wrapping an App that is already local returns it unchanged, so a caller that wraps defensively does not stack registries.
Types ¶
type AuditRequest ¶
type AuditRequest struct {
RepoURL string `json:"repo_url"`
Depth string `json:"depth"`
Branch string `json:"branch"`
CommitSha *string `json:"commit_sha"`
BaseCommitSha *string `json:"base_commit_sha"`
SeverityThreshold string `json:"severity_threshold"`
ScanTypes []string `json:"scan_types"`
OutputFormats []string `json:"output_formats"`
ComplianceFrameworks []string `json:"compliance_frameworks"`
MaxCostUsd *float64 `json:"max_cost_usd"`
MaxProvers *int `json:"max_provers"`
MaxDurationSeconds *int `json:"max_duration_seconds"`
IncludePaths []string `json:"include_paths"`
ExcludePaths []string `json:"exclude_paths"`
IsPr bool `json:"is_pr"`
PrID *string `json:"pr_id"`
PostPrComments bool `json:"post_pr_comments"`
FailOnFindings bool `json:"fail_on_findings"`
// EnableDast is accepted and then DISCARDED — see ToAuditInput.
EnableDast bool `json:"enable_dast"`
ResumeFromCheckpoint *string `json:"resume_from_checkpoint"`
// CheckpointDir is where the phases' checkpoints are written, in place of
// the `.sec-af` folder inside the repository. It is codeaf's, never on the
// wire: an audit run by codeaf promises the person their folder back as it
// found it, so its checkpoints go to the run's record folder. Empty keeps
// sec-af's own place.
CheckpointDir string `json:"-"`
}
AuditRequest transcribes the `audit(...)` signature (app.py:124-144) — all twenty parameters, with their exact names, types and defaults.
Nil pointers stand for Python's `None`; nil slices stand for the four `list[str] | None = None` parameters, whose `or` fallbacks live in ToAuditInput.
RepoURL keeps its wire name, but inside codeaf it must name a local directory: ResolveRepo refuses a URL rather than cloning it.
func BindRequest ¶
func BindRequest(input map[string]any) (AuditRequest, error)
BindRequest turns an untyped keyword map into an AuditRequest the way the node's `audit` reasoner did, in Python's order:
validated = self._validate_handler_input(body, input_fields) # SDK audit_input = AuditInput(**validated) # pydantic
The SDK-level validation is what turns `{"is_pr": "yes"}` into true and `{"repo_url": 5}` into "5" instead of a bind error, and what rejects an explicit null on a required parameter. Its refusal is an *Error with 422, the status Python's endpoint answered with; a failure of the typed bind that follows is an *Error with 400.
func NewAuditRequest ¶
func NewAuditRequest() AuditRequest
NewAuditRequest returns the three non-empty keyword defaults of audit(): depth="standard", branch="main", severity_threshold="low". Every other parameter's default is None / [] / False, which is the Go zero value.
func (AuditRequest) ToAuditInput ¶
func (a AuditRequest) ToAuditInput() schemas.AuditInput
ToAuditInput ports the `AuditInput(...)` construction at app.py:146.
Python parity, three points:
- the four list fallbacks are `x or [default]`, i.e. PYTHON TRUTHINESS, not `is None`. An EXPLICIT EMPTY LIST therefore also falls back to the default: `scan_types=[]` yields ["sast","sca","secrets","config"], and `exclude_paths=[]` yields the four-entry default. There is no way to ask for "no exclusions" through this entry. Reproduced.
- `include_paths` has NO fallback: it is forwarded as-is, so None stays None (scan everything) and [] stays [] (an empty include filter).
- `enable_dast=enable_dast` is passed to a model that has NO `enable_dast` FIELD — AuditInput declares `dast_enabled`. pydantic's default `extra="ignore"` DROPS it silently, so `dast_enabled` is False for every request no matter what the caller sends. This is a live Python bug — the `enable_dast` parameter is inert — and it is reproduced rather than fixed: DastEnabled is left at the pydantic default.
The five fields audit() does not pass at all (dast_enabled, repo_urls, monitoring_mode, baseline_path, custom_policies) keep their pydantic defaults, which is what starting from schemas.NewAuditInput() gives.
func (*AuditRequest) UnmarshalJSON ¶
func (a *AuditRequest) UnmarshalJSON(b []byte) error
UnmarshalJSON seeds audit()'s keyword defaults before decoding.
type CallError ¶
CallError is a `.call` that did not succeed. Its text is exactly the failing reasoner's own message — the error_message the control plane reported — because phases fold that text into fallback findings and notes.
type Error ¶
Error is an audit that produced no result. StatusCode keeps the HTTP status the node answered with, because it is how a caller tells the three outcomes apart:
422 the request failed the SDK-level input validation (BindRequest)
400 the request was refused: it did not bind, it names no usable
directory, or the pipeline raised one of Python's ValueError family
(see IsBadInput) — Message is the raw text
500 the audit itself failed — Message carries Python's
"audit execution failed: " prefix when the failure came from inside
the pipeline
Err is the underlying cause, for errors.Is and errors.As.