Authorization Bypass in gRPC-Go HTTP/2 Path Validation (CVE-2026-33186)
Introduction
The src/go/go.mod file in this Moby-based containerized service pulls in google.golang.org/grpc as an indirect dependency — one of the most widely used RPC frameworks in the Go ecosystem. Because it sits behind an // indirect comment, it's easy to overlook during routine dependency audits. That oversight nearly cost this project dearly: grpc-go v1.79.1 ships with a critical authorization bypass (CVE-2026-33186) rooted in how the authz middleware validates HTTP/2 :path pseudo-headers.
This post breaks down exactly what went wrong, why HTTP/2 path handling is a surprisingly tricky security surface, and how a two-line change to go.mod and a checksum update in go.sum sealed the gap.
The Vulnerability Explained
gRPC Authorization and the HTTP/2 :path Header
gRPC-Go's google.golang.org/grpc/authz package provides policy-based authorization for RPC methods. Under the hood, every gRPC call maps to an HTTP/2 request where the method path — e.g., /mypackage.MyService/MyMethod — is carried in the :path pseudo-header.
Authorization rules are typically written against these path strings:
// Example authz policy (simplified)
{
"rules": [{
"name": "block-admin",
"source": {"principals": ["*"]},
"request": {"paths": ["/admin.AdminService/*"]}
}]
}
The vulnerability lies in how authz normalizes the :path value before matching it against policy rules. In v1.79.1, certain malformed or non-canonical path representations — such as paths with unexpected percent-encoding, double slashes, or other HTTP/2-legal-but-semantically-ambiguous constructs — were not consistently normalized. This created a mismatch: the policy engine evaluated one string while the actual RPC dispatcher resolved another.
The Exploitable Pattern
Consider a service that protects /admin.AdminService/DeleteUser behind an authorization rule. An attacker who can send raw HTTP/2 frames could craft a :path value like:
/admin.AdminService/%44eleteUser
or
//admin.AdminService/DeleteUser
The authz middleware, operating on the raw or inconsistently normalized value, might not match the deny rule — passing the request through to the RPC handler as if it were authorized. The dispatcher, which performs its own normalization, resolves the path correctly and executes the privileged method.
This is a classic TOCTOU-style normalization mismatch: the security check and the execution engine disagree on what the path actually means.
Why This Matters for a Containerized Moby Service
In a containerized environment, gRPC endpoints are often exposed on internal networks with the assumption that the authorization middleware is the primary access control layer. If that layer can be bypassed by a network-adjacent attacker — or even a compromised container on the same overlay network — the blast radius is significant. Any RPC method behind an authz policy could be reachable without credentials or role membership.
The Trivy scanner flagged this in src/go/go.mod with severity CRITICAL and assessment "Likely exploitable", indicating the vulnerable code path is reachable in the production build.
The Fix
What Changed
The fix is surgical: a single version bump in src/go/go.mod and the corresponding checksum entries in src/go/go.sum.
src/go/go.mod — before:
google.golang.org/grpc v1.79.1 // indirect
src/go/go.mod — after:
google.golang.org/grpc v1.79.3 // indirect
Additionally, a previously missing transitive dependency was pinned explicitly:
// Added to the indirect require block:
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
src/go/go.sum — new checksums added:
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:...
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:...
Why Each Change Was Necessary
| Change | Purpose |
|---|---|
grpc v1.79.1 → v1.79.3 |
Pulls in the patched authz path normalization logic |
Added genproto/googleapis/rpc explicit pin |
v1.79.3 tightened its RPC type dependencies; pinning ensures reproducible builds and prevents the module graph from silently resolving an older, potentially vulnerable version |
Updated go.sum hashes |
Go's module verification requires cryptographic checksums for every resolved module version; without these, go build would refuse to proceed |
The grpc-go v1.79.3 patch introduces canonical normalization of the HTTP/2 :path pseudo-header inside the authz interceptor before policy evaluation, ensuring the string the security check operates on is identical to the string the dispatcher will use. The fix eliminates the normalization mismatch at its source.
Prevention & Best Practices
1. Treat Indirect Dependencies as First-Class Security Concerns
The vulnerable line in go.mod was marked // indirect — meaning no first-party code directly imports grpc. It's a transitive dependency pulled in by another library. Indirect dependencies are just as exploitable as direct ones and must be included in vulnerability scanning.
Action: Configure Trivy, govulncheck, or Dependabot to scan all dependencies, not just direct imports.
# Run govulncheck on your module
govulncheck ./...
# Or use trivy on your go.mod directly
trivy fs --scanners vuln src/go/go.mod
2. Pin and Audit gRPC Authorization Middleware Separately
If you use google.golang.org/grpc/authz, treat it as a security-critical component. Add it as a direct dependency in go.mod even if it's technically indirect, so version changes are explicit and visible in code review:
require (
google.golang.org/grpc v1.79.3
// Explicitly track authz-bearing grpc version
)
3. Validate HTTP/2 Path Inputs at the Application Layer Too
Even with a patched framework, defense-in-depth recommends validating RPC method paths at the application layer:
// In your gRPC server interceptor
func pathValidationInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
method := info.FullMethod
if !isCanonicalGRPCPath(method) {
return nil, status.Errorf(codes.InvalidArgument, "malformed method path: %s", method)
}
return handler(ctx, req)
}
func isCanonicalGRPCPath(path string) bool {
// Must start with /, no double slashes, no percent-encoding
return strings.HasPrefix(path, "/") &&
!strings.Contains(path, "//") &&
!strings.ContainsRune(path, '%')
}
4. Use go mod tidy + go mod verify in CI
# Ensure the module graph is consistent
go mod tidy
# Verify all downloaded modules match go.sum checksums
go mod verify
Failing CI on go mod verify discrepancies catches tampered or unexpected dependency versions before they reach production.
5. Relevant Standards and References
- CWE-863: Incorrect Authorization
- CWE-116: Improper Encoding or Escaping of Output (path normalization failure)
- OWASP ASVS v4.0, Section 4.1: Access Control Design Requirements — specifically, that access control checks must use the same representation of a resource as the enforcement point
- gRPC Security Best Practices: https://grpc.io/docs/guides/security/
Key Takeaways
// indirectingo.moddoes not mean "low risk" — CVE-2026-33186 lived in an indirect dependency and was rated CRITICAL. Scan everything.- HTTP/2 path normalization is a security boundary — the
authzmiddleware in grpc-go v1.79.1 evaluated paths before canonicalization, creating a gap between the security check and the dispatcher. v1.79.3 closes this by normalizing first. - The
genproto/googleapis/rpcpin was not cosmetic — explicitly adding it togo.modensures the build graph is deterministic and that no older RPC type package can be silently resolved, which could reintroduce instability in the patched code paths. - Authorization bypass vulnerabilities in RPC frameworks are high-value targets in containerized environments where gRPC endpoints are assumed to be protected by middleware alone.
- A two-line diff can fix a critical vulnerability — but only if your scanner is configured to catch it. The Trivy rule
CVE-2026-33186found this; without automated scanning, it would have shipped.
Conclusion
CVE-2026-33186 is a reminder that the security of your gRPC service is only as strong as the path normalization logic in your authorization middleware. In this case, google.golang.org/grpc v1.79.1 — quietly sitting in src/go/go.mod as an indirect dependency — contained a flaw that could allow an attacker to bypass carefully crafted authorization policies by sending a semantically equivalent but syntactically non-canonical HTTP/2 path.
The fix is minimal in code footprint but significant in impact: upgrading to v1.79.3 ensures the authz interceptor and the RPC dispatcher always agree on what path is being requested. Combined with the explicit genproto/googleapis/rpc pin for build reproducibility, this change closes the vulnerability cleanly.
If your Go services use gRPC — directly or transitively — audit your go.mod today. Run govulncheck ./... and make sure your indirect dependencies are held to the same security standard as your first-party code.
This vulnerability was identified and remediated by Orbis AppSec automated security tooling.