GCA · LANGUAGE GUIDES

    Go

    Deep-dive: Go is a primary language with a dedicated taint analyzer.

    Go — Deep-Dive Guide

    Go is a primary language for Gadriel Code, with a dedicated taint analyzer, an entry-point policy tuned to Go web frameworks, cross-file (workspace) tracking on by default, and optional gosec corroboration. This guide goes below the summary in languages.md — read that first for the capability matrix, and scan-coverage.md for the six scan types.

    The Go front-end (go.rs) binds tree-sitter-go; the taint engine lives in taint_go/ with a cross-file coordinator in workspace_taint/go.rs.


    Which scans apply, and how deep

    Scan typeApplies?Depth
    SASTDedicated Go taint analyzer with a framework-aware entry-point policy; intra-file N-hop interprocedural + cross-file workspace pass.
    SCAgo.sum parser (see limits below).
    SecretsLanguage-agnostic.
    ConfigLanguage-agnostic.
    APIOpenAPI/GraphQL + source route-handler extraction.

    The taint engine

    Sources it recognizes

    Known-input calls (GO_KNOWN_INPUT_CALLS) — substring-matched on an assignment RHS, covering the standard library and the major frameworks:

    net/http:  r.URL, r.URL.Query, r.FormValue, r.PostFormValue, r.Body,
               r.Header, r.Cookie, r.RemoteAddr, r.Host, req.*, request.*
    gorilla:   mux.Vars
    gin:       c.Query, c.Param, c.PostForm, c.GetRawData, c.ShouldBindJSON, c.BindJSON
    echo:      c.QueryParam, c.FormValue, c.Bind
    fiber:     c.Query, c.Params, c.Body, c.BodyParser
    chi:       chi.URLParam
    Lambda:    event.QueryStringParameters, event.Body, event.PathParameters
    std:       os.Getenv, os.Args, os.Stdin, bufio.NewScanner
    

    Provenance tiering: os.GetenvEnv; os.Args/os.Stdin/bufio.NewScannerLocalCli; everything else (request-bound values) → Remote.

    Deserialization propagators (GO_UNMARSHAL_CALLS) — the (src, &dst) shape taints dst: Unmarshal, UnmarshalJSON/YAML/XML, Decode, BodyParser, BindJSON, Bind, ShouldBindJSON, ShouldBind.

    Entry-point policy

    Because Go handlers are ordinary functions, the default heuristic policy (DecoratorAndNameHeuristic) classifies entry points several ways, and every parameter of a classified function is seeded as Remote taint:

    • Exact namesServeHTTP, Handler, main, lambdaHandler, index.
    • Name affixes — prefix Handle/handle, suffix Handler/Handle.
    • Request-shaped parameter — a parameter named r/req/request/c/ctx whose annotation contains a known request type (http.Request, gin.Context, echo.Context, fiber.Ctx, events.APIGatewayProxyRequest, …). context.Context is explicitly excluded unless it is an API Gateway event.
    • Registered closures — a closure passed to app.HandleFunc, r.GET, router.Use, etc.
    • gRPC — a method whose receiver type contains Server and whose parameter is a package-qualified ...Request message.

    A deliberate FP fix: bare local Request/*Request types are not request-bound — only package-qualified (protobuf) messages are — so a helper named exportCurl(req *Request) is not treated as an HTTP entry point.

    Sink families and finding classes

    Sinks live in the GVL rule corpus, not the Go source; the engine surfaces the dotted callee name so a rule can match it. The exception is the open-redirect gate, which is hard-coded because it needs argument-position logic.

    ClassSink shapeCWE
    SQL injectiondb.Exec(fmt.Sprintf("... %s", uid))CWE-89
    Command injectionexec.Command("sh", "-c", cmd) (os/exec)CWE-78
    Open redirecthttp.Redirect(w, r, target, code) / c.Redirect(code, loc)CWE-601
    Path traversalhttp.ServeFile(w, r, p) with a request-bound pCWE-22
    Insecure deserializationjson.Unmarshal(body, &u) from r.BodyCWE-502
    func handleUser(w http.ResponseWriter, r *http.Request) { // entry point
    uid := r.URL.Query().Get("id") // Remote source
    db.Exec(fmt.Sprintf("SELECT * FROM u WHERE id=%s", uid)) // CWE-89
    }
    cmd := r.FormValue("cmd")
    exec.Command("sh", "-c", cmd) // CWE-78

    Taint propagates through fmt.Sprintf/Errorf, string concatenation, method calls on tainted receivers, and multi-return destructuring — all via a lexical identifier scan, not typed sink modeling. Every go fn() surfaces as a synthetic goroutine call so concurrency rules can match.

    Cross-file (workspace) taint — on by default

    Go has package-level cross-file taint, on by default for directory scans under the same --no-workspace-taint switch as the other workspace languages (active banner: SAST: workspace-aware (cross-file Python + JS/TS + Rust + Go taint enabled)).

    • Cross-package calls (helpers.BuildQuery(uid)) are recorded and resolved through a workspace function index built from import specs.
    • The fixed-point loop is bounded: 12 rounds, ceilings of 1,200 files / 12,000 functions, and a 10-second wall-clock budget; exceeding a ceiling falls back to per-file taint.
    • A callee parameter inherits the caller's dominant provenance tier, so os.Args/os.Getenv sources keep LocalCli/Env across the boundary rather than being upgraded to Remote.

    Honest cross-file limits: only ./ and ../ relative import paths resolve — github.com/..., GOPATH, vendor/, and stdlib are unresolved; only function_declaration targets are indexed (cross-package methods are out of scope); and only two-segment pkg.Func() selector shapes are handled.

    Sanitizer / guard recognition

    Structural dominance-proof guard analysis exists (taint_go/guard.rs) with early-exit kinds return, break, continue, goto, but ships inert in production: no shipped Go rule declares dominating_guards:, and the guard runs only as a log-only observational signal — the token-scan stays authoritative. Sanitized reassignments are recognized at the matcher sink gate via each rule's sanitizers: list and sanitize_*/escape_*/html.EscapeString heuristics.


    SCA — go.sum

    Go dependency analysis parses go.sum only, emitting Ecosystem::Go, feeding OSV/license/health/reachability. Entries are deduped on (module, version), with the /go.mod suffix stripped.

    Limit: go.mod is not parsed, so there is no direct-vs-transitive distinction — every dependency is marked Direct. A go.mod cross-check is a documented deferred item.


    Tier-1 corroboration with gosec

    Go is the best example of Gadriel's Tier-1 aggregation model (see tiers.md). When you pass --corroborate (opt-in, off by default; also [scan] corroborate in .gadriel.toml), Gadriel runs your locally installed gosec over the scan root (gosec -fmt=sarif ./...) and folds its findings in:

    • gosec findings are tagged Tier1External, kept at Medium confidence (never High by assumption), and mapped to CWE/OWASP codes (G201/G202→SQLi, G204→command injection, G304→path traversal, G401→weak crypto, …).
    • Merge is additive, not suppressive. On overlap (same path, line ±3, CWE family) the higher-confidence finding is kept and the external one is attached as corroborated_by; a strictly-higher-confidence external finding replaces the in-house one. Disjoint gosec findings are appended — most of the value is coverage the in-house Go engine does not have (e.g. weak-crypto G401, hardcoded-credential G101).
    • Tier-1 is advisory — in code doctor a missing or too-old gosec is a warning, never a gate failure. This is the framework generalizing the clang precedent to gosec/bandit/eslint.

    False positives, provenance, and test scope

    Go carries some of the most targeted FP gates in the scanner:

    • Open-redirect three-gate — the tainted value must occupy the actual destination argument; there must be a genuine Remote request source (a sibling of an *http.Request parameter does not count); and a same-origin .URL.String() target whose host/scheme are never assigned is treated as same-origin by construction.
    • has_genuine_request_source tier check — env/CLI sources (os.Getenv, os.Args, os.Stdin) do not satisfy the open-redirect gate.
    • context.Context exclusion and bare-local-Request exclusion cut common false entry points.
    • Test scope — Go test detection is path-based. **/*_test.go is a hard skip (Go excludes _test.go from production builds, so a vuln there is unexploitable); **/*.pb.go (generated) is also skipped. Other test paths are demoted a band.
    • Provenancedominant_tier() takes the most-dangerous tier present; demote: [local_cli]/[env] rules down-weight os.Args/os.Getenv flows.
    • Low-confidence SAST is advisory, as for every language.

    Honest scope limits

    • go.mod not parsed — no direct/transitive distinction in SCA.
    • Flow-insensitive — a re-clean is not tracked; relies on matcher sanitizers.
    • Interprocedural — intra-file N-hop for function_declaration bare-identifier callees only; method calls, index/closure callees, and goroutine-captured closures are not followed intra-file (cross-package only via the workspace coordinator).
    • Cross-file — only relative imports; remote/vendored packages and cross-package methods are out of scope.
    • Return-value back-propagation is not modeled cross-function; multi-return is approximated by lexical over-tagging.
    • No Go rule ships dominating_guards — structural guard analysis is inert (log-only) in production.
    • Argument extraction is Phase-1 stubbed (only positional argument shapes).
    • Sink coverage is rule-defined, not engine-defined.
    • gosec corroboration is experimental, opt-in, advisory, and Medium-capped.

    • languages.md — the cross-language capability matrix.
    • scan-coverage.md — the six scan types and eight pillars.
    • false-positives.md — confidence, effective risk, provenance, waivers.
    • tiers.md — the three-tier model and gosec Tier-1 corroboration via --corroborate.