Pular para conteúdo

AUR-577: scanners are registered engines

Measured before (base 85209c53, audit of 2026-10-03)

  • Adding a scanner touched 12-14 files and 5 lists: the const/switch of gate.sources (config.GateConfig.ValidateSources), the fixed pointer QualityGatesConfig.Sast, the quality_gates.sast block of ApplyCentralPolicy, gate.OriginSAST, the dedicated gate.SASTContributor{SectionOrigin, Issues, Reason}, the SAST* fields of gatePipelineInputs, runSASTPass/realSemgrepRunner in cmd/aurumcode/sast_pass.go, the session's runSAST, RankReason's SASTReason.
  • SastConfig.Engine was decorative: EngineName() returned "semgrep" and the runner called analysis.NewRunner().Semgrep without reading it.
  • The SARIF rebuilt each finding's origin by a rule|path|line join with BlockingFindings and dropped every blocking finding no review issue carried: the Dependency-Track breach (ssor_dtrack) was in the audit and missing from the SARIF.
  • fmt.Sprintf("%s (rule %s)", f.Message, f.RuleID) was written in three places (gate/sources.go, cmd sast_pass.go, cmd passes.go).
  • The Semgrep execution lived in internal/analysis/semgrep.go, beside the embedded regex catalog and go vet.

What changed

  • internal/scanner (new): Scanner{Name(); Run(ctx, Request) (Report, error)}, Report{Findings, Complete, Version}, Finding.ToIssue(origin) (the one conversion; analysis.Finding is now an alias of scanner.Finding), Request{Root, Trust, Options, Command}, the registry (Register, Lookup, Names, NamesIn, Categories, KnownSource) of Engine{Scanner, Category, Origin, Validate}, and the Executor that runs an engine with a timeout and turns an error, a missing binary or Complete=false into the inconclusive reason <category or name>_{unavailable,execution_error,invalid_output,incomplete} (FailureReason), never into zero findings.
  • internal/scanner/semgrep: the Semgrep engine (code moved from internal/analysis/semgrep.go with its tests, plus the PATH runner from cmd). Category sast, origin sast, option rule_packs. The policy flags --disable-nosem and --x-ignore-semgrepignore-files are kept under Trust: TrustPolicy. internal/scanner/engines is the closed list of compiled engines (one blank import each); internal/config imports it, so every binary that parses a configuration holds the engines.
  • internal/config: quality_gates.scanners: [{engine, enabled, required, fail_on, options}] (scanners.go); quality_gates.sast is read as the semgrep entry (SastConfig.AsScanner, always required), and its engine must be a registered engine of category sast. Parse refuses an unknown engine (quality_gates.scanners[<engine>].engine: unknown engine ... (registered: semgrep)), an engine declared twice (sast alias included), a bad fail_on and options the engine refuses. mergeScanners (scanners_policy.go) replaces the quality_gates.sast block of ApplyCentralPolicy: engine by engine, a required policy entry wins and the repository's entry (including enabled: false) becomes the warning <key> do config do repositório foi ignorado: a política central decide sozinha (the same text as before for quality_gates.sast); a policy entry that is not required yields to the repository with its own named warning. FromPolicy(engine) records which effective entries are the policy's. gate.sources and gate.triage validate against skills, analysis and the registry (engine names and categories); sast remains the category of every SAST engine, and the error text keeps listing skills, analysis, sast.
  • internal/gate: sast.go became scanner.go with Scan (entry, engine, section, issues, reason) and ApplyScannerGate; ScannerContributor{Scans} replaces SASTContributor (pipeline name scanners); RankReason reads ScannerReason. SASTIssues, FoldSources and OriginSAST are gone.
  • cmd/aurumcode: scanner_pass.go replaces sast_pass.go: runScanners runs every enabled entry through reviewDeps.scanners (a scanner.Executor; the injected runner of the session is now the executor), joinScanners states each inconclusive scan. The gate phase passes the scans to the one contributor, the triage maps each scan by the engine's Answers. Nothing in cmd or gate names an engine.
  • internal/render/gatefindings.go: GateSARIFFindings.

One gate finding for the audit and the SARIF (AC-005)

Result.BlockingFindings is the one record of what counted: the audit's blocking_findings is written from it, and the SARIF now reads every origin from it too. The SARIF still lists the review's issues (they carry the message, the code context for the fingerprint, the assessment and the suppression, which the gate finding does not); the match by rule, path and line only attaches the gate finding's origin to that issue. What changed is that a blocking finding no issue carries is no longer dropped: it becomes its own SARIF result with its origin (ssor_dtrack, origin dtrack, message ssor_dtrack (origem dtrack)).

Observable output

The Semgrep engine keeps the origin sast: the gate line (origem sast, secao <policy|repo>), the inconclusive line (SAST (semgrep, origem sast, secao repo) inconclusivo (sast_unavailable)), the limitation (SAST (Semgrep) inconclusivo: ...), the audit and the SARIF are byte-identical for every existing configuration, so AUR-497's block and the gate and sast tutorials did not have to change. A new engine's origin is its name.

Divergence recorded

AC-002 says exit 2 for an unknown engine. The product exits 1 on every configuration load error, as AUR-575 already recorded; this card keeps that code and TestAUR577UnknownEngineStopsTheReview asserts exactly 1 with the engine named on stderr and no report printed.

Re-anchored done acceptances (card v3 paths)

Script Was Now
AUR-548 sources cmd/aurumcode/sast_pass.go, internal/analysis/semgrep.go; MUT marker in internal/analysis/semgrep.go, replacement return []Finding{}, nil sources cmd/aurumcode/scanner_pass.go, internal/scanner/semgrep/{semgrep,engine}.go; same marker in internal/scanner/semgrep/semgrep.go, replacement return []scanner.Finding{}, nil
AUR-567 internal/gate/sast.go, name, OriginSAST+", secao "+origin) -> name, origin) internal/gate/scanner.go, name, origin+", secao "+s.Section) -> name, s.Section)
AUR-575 MUT-003 internal/gate/sast.go internal/gate/scanner.go (same line)

How each AC/MUT is proven

bash tests/acceptance/AUR-577.sh all in the shared container:

AUR-577/AC-001/pass
AUR-577/AC-002/pass
AUR-577/AC-002/sast-demo/pass
AUR-577/AC-003/pass
AUR-577/AC-004/pass
AUR-577/AC-005/pass
AUR-577/MUT-001/rejected
AUR-577/MUT-002/rejected
AUR-577/all/pass
  • AC-001: TestAUR577FakeEngineReachesEverySinkByOrigin registers fakescan only in the test binary, declares it in quality_gates.scanners and runs the real review --base: the gate line ends origem fakescan, secao repo, the audit's blocking_findings and the SARIF's properties.origin say fakescan. TestAUR577NoEngineSwitchInGateOrCmd parses (go/ast) every production file of internal/gate and cmd/aurumcode: no case, ==/!= or call argument is a registered engine name or category literal, and neither imports an engine package.
  • AC-002: TestAUR577UnknownEngineRefusedAndSastIsAlias (Parse error naming quality_gates.scanners[inexistente].engine; quality_gates.sast is the semgrep entry; the same engine twice is refused; sast/semgrep are gate sources), TestAUR577UnknownEngineStopsTheReview (exit 1, no report), the legacy TestAUR548SeverityBreachFailsGate/CleanScanPasses, and demo/tutoriais/sast/run.sh --check after re-recording: the sast expected/ did not change.
  • AC-003: TestAUR577PolicyRequiredScannerSurvivesRepositoryDisable: the policy's required: true entry runs once under section policy although the repository wrote enabled: false (with and without its own required: false), the override is the named warning; a policy entry that is not required yields, with its own warning. MUT-002 (if declared && p.Required && false) turns it RED by assertion.
  • AC-004: TestAUR577MissingBinaryAndIncompleteAreInconclusive: an engine whose binary is absent is ghostscan_unavailable, one whose report is Complete: false is halfscan_incomplete; no issues, the gate is inconclusive and fails (block by default). Plus TestAUR548AbsentSemgrepIsInconclusiveNeverClean.
  • AC-005: TestAUR577AuditAndSARIFShareTheGateFindings: ssor_dtrack (origin dtrack) and a semgrep finding appear in the audit and in the SARIF with the same origin.
  • MUT-001: the pass ignores engine and always looks up semgrep: TestAUR577FakeEngineReachesEverySinkByOrigin fails (exit 1, no fakescan finding) and the AST test fails on the literal.

Regression (shared container, in series)

go vet ./... && go test -p 1 ./...: 36 packages ok, no FAIL. AUR-548.sh all -> AUR-548/all/pass (re-anchored mutation RED); AUR-567.sh all -> AUR-567/all/pass (re-anchored mutation RED); AUR-575.sh all -> AUR-575/all/pass (MUT-003/red: --- FAIL: TestAUR575UnknownSeverityCountsAsError); AUR-556, AUR-569, AUR-570, AUR-576, AUR-579, AUR-497 all pass; AUR-561..564 all rc 0 (last line AC-002-MUT-001/ok). bash scripts/docs/build.sh (strict) built with no warning.

Tutorials: image aurum-tutoriais:aur577 built from this tree, the 15 tutorials re-run in series (run.sh all, then --check): all=0 check=0 for every one. No expected/ file and no docs/tutorials block changed (no tutorial writes a SARIF with a Dependency-Track breach); out/ (with .imagem) is committed.