
Two weeks ago I wrote up CVE-2026-33186, an authorization bypass in grpc-go where a single missing slash in the HTTP/2 :path pseudo-header skipped interceptor checks. The fix in upstream was two lines. What took me longer was writing the detection rule for it.
The vulnerable pattern is specific: an interceptor that calls ServerTransportStream.Method() and string-compares the result without normalizing the leading slash. The rule needed to flag only that call site, only on a receiver of type google.golang.org/grpc.ServerTransportStream, and nothing else. Pattern matching can't do that. Method() is defined on half the interfaces in the Go standard library, so a grep-based scanner buries the real finding under hundreds of unrelated hits.
That gap is what pushed me to finish Go support in Code Pathfinder. It ships today in v2.1.0: 21 security rules, 204 Go classes documented in the SDK reference (66 with verified source/sink annotations, covering Gin, Echo, Fiber, GORM, sqlx, gRPC, and the Go standard library). Warm scans are 6.6x faster than cold.
How Go analysis is different from Python
Go has a method-naming problem. Raw() means one thing on a gorm.DB and a completely different thing on an html/template. Method() is defined on half the interfaces in the standard library. Execute() shows up wherever something can be run. A SAST tool that doesn't read types has to choose between flagging every hit as a finding (noise) or ignoring the whole surface (false negatives).
The compiler already knows the concrete type behind every value. Go is static and interface-driven: any struct with the right method set satisfies an interface with no declaration needed. The type information is sitting in the AST the whole time. Most Go SAST tools just don't read it. They grep for Raw() and emit a finding per hit, and the noise pile keeps growing. Security teams tune out by day two.
Python had the opposite problem. Everything is dynamic, so name matching is often all you have. Go gives you the compiler for free. You just have to use it.
QueryType uses it. Every source and sink in a Go rule is bound to a fully-qualified Go module path. GoGormDB is bound to gorm.io/gorm. GoGRPCServerTransportStream is bound to google.golang.org/grpc. When the engine walks the call graph, it resolves the concrete receiver type on each method call and only matches when that type matches an entry in the FQN list. A Method() call on anything that isn't a grpc server transport stream gets dropped silently.
You can audit whether a rule is actually using type inference. Change fqns to a garbage string and re-run the rule. If findings drop to zero, type inference is load-bearing. If findings survive, the rule fell back to pattern matching and needs tightening. That's the test I run against every rule we ship.
The incremental cache
Large Go codebases are slow to scan cold. File-local work (extracting statements, building CFGs, inferring types) caches cleanly. Hash the file, store the output in SQLite, skip the file next time if nothing changed.
Cross-file resolution is trickier. A file that hasn't changed still needs re-analysis if one of its callees changed. Each file tracks which external functions it calls, and if any of those changed since the last scan, that file re-runs cross-file resolution. Otherwise it comes from cache. A PR touching 5 files out of 400 runs in roughly the time it takes to process those 5 files plus their direct callers.
The SQLite schema is versioned. When the summary format changes, the schema version bumps and the cache clears itself. No stale reads.
What ships in v2.1.0
21 rules across the categories that show up most in real Go security reviews. Each rule links to its registry page with a live playground.
| Rule | Category | Detects |
|---|---|---|
| GO-SEC-001 | SQL injection | User input into database/sql raw queries |
| GO-GORM-SQLI-001 | SQL injection | User input into GORM Raw() / Exec() |
| GO-GORM-SQLI-002 | SQL injection | GORM query builder with string concatenation |
| GO-SQLI-003 | SQL injection | User input into sqlx raw queries |
| GO-SSRF-001 | SSRF | User input into go-resty HTTP request methods |
| GO-SSRF-002 | SSRF | User input into net/http client calls |
| GO-SEC-002 | Command injection | HTTP input into os/exec (Gin, Echo, Fiber, Chi, Mux) |
| GO-XSS-001 | XSS | Unsafe writes to http.ResponseWriter |
| GO-XSS-002 | XSS | Unsafe type conversions bypassing html/template |
| GO-XSS-003 | XSS | Unescaped user input in template rendering |
| GO-CRYPTO-001 | Weak crypto | MD5 usage |
| GO-CRYPTO-002 | Weak crypto | SHA1 usage |
| GO-CRYPTO-003 | Weak crypto | DES usage |
| GO-CRYPTO-004 | Weak crypto | 3DES usage |
| GO-CRYPTO-005 | Weak crypto | RC4 usage |
| GO-JWT-002 | Auth | JWT signature verification skipped (golang-jwt) |
| GO-NET-001 | Network | HTTP server started without TLS |
| GO-NET-002 | Network | gRPC client without transport security |
| GO-PATH-001 | Path traversal | User-controlled path into file operations |
| GO-REDIRECT-001 | Open redirect | User-controlled URL in redirect calls |
| GO-SEC-004 | Credentials | Hardcoded credentials in variable assignments |
Here's GO-GORM-SQLI-001 running against a vulnerable Gin handler. User input from a query parameter flows directly into db.Raw() without parameterization:
Writing your own Go rules
A Go rule in Code Pathfinder is a Python function decorated with @go_rule. The sources and sinks are QueryType method selectors bound to Go module FQNs.
GoGinContext resolves to github.com/gin-gonic/gin. GoGormDB resolves to gorm.io/gorm. GoStrconv.method("Atoi") resolves to strconv.Atoi in the standard library. When user input flows from a Gin handler into a GORM Raw call without passing through strconv.Atoi, it reports a finding.
PropagationPresets.standard() enables taint through assignments, function arguments, return values, string concatenation, and format strings. scope="global" extends tracking across file boundaries through the call graph.
66 QueryType classes ship with verified source, sink, and sanitizer annotations. Web frameworks: Gin, Echo, Fiber, gRPC, Gorilla Mux, Chi. Database libraries: database/sql, GORM, sqlx, pgx, mongo-driver, go-redis. HTTP clients: net/http, Resty. Authentication: golang-jwt. Cryptography: md5, sha1, des, rc4, aes, x509. Filesystem: os, os/exec, path/filepath. The rest of the Go SDK reference covers 138 more stdlib and third-party packages as auto-indexed stubs.
SDK documentation and the FQN browser
Writing rules in the dark is painful. You need to know which class wraps which module path, which methods are sources vs. sinks, and whether your receiver type is annotated at all. The SDK documentation is the answer. Every Go class has its own page with the full FQN list, every method with its Go signature, and a verified role (source, sink, sanitizer, or neutral).
For ad-hoc lookups, the FQN browser indexes every class across Go and Python in a single searchable surface. Drop in gorm, gin.Context, or jwt and you land on the class page in one click. Each result shows the method count, security roles, and the canonical import.
Two things that are easy to miss:
- Auto-indexed stubs have no security roles yet. If a page shows all methods as neutral with a "method-level security roles have not been annotated" banner, the class came in from the CDN registry and no one has written a taint rule against it. That doesn't mean the class is useless. The FQN and method signatures are still correct, so it's a real target for code-quality rules, architectural linters, library-usage policies, deprecation checks, or any other static analysis you want to run against Go code. Security annotations are just one layer.
- The API reference is the lookup table for the primitives themselves (flows, QueryType, .method, .attr, .where, .tracks, PropagationPresets). It's separate from the per-class SDK pages. Use it when you're trying to remember whether .where takes a qualifier or a literal.
If you find a class you need and it's still a stub, annotating it is a small PR: fill in roles on the methods you care about, add an example rule, open a PR. The contributor guide walks through the workflow.
Getting started
Install:
Scan your Go project:
GitHub Actions integration:
Add it to CI so every pull request gets scanned automatically. Code Pathfinder has integrations for GitHub Actions, GitLab CI, CircleCI, Jenkins, and more.
Browse all 21 Go rules at the Go rules registry. Each rule has a vulnerable/secure example pair and an interactive playground.
The rules and engine are open source. If you're working on Go security and want to contribute rules or framework coverage, that's the place.