# GO-NET-002: gRPC Client Without TLS

> **Severity:** HIGH | **CWE:** CWE-300, CWE-319 | **OWASP:** A02:2021, A07:2021

- **Language:** Go
- **Category:** Security
- **URL:** https://codepathfinder.dev/registry/golang/security/GO-NET-002
- **Detection:** `pathfinder scan --ruleset golang/GO-NET-002 --project .`

## Description

gRPC (google.golang.org/grpc) is the dominant RPC framework for Go microservices.
By default, gRPC requires transport security. Developers explicitly opt out using
`grpc.WithInsecure()` (deprecated in gRPC-Go v1.35+, March 2021) or the replacement
`grpc.WithNoTLS()` (insecure.NewCredentials()) — both disable TLS entirely.

Without TLS, all gRPC communication travels unencrypted:
- Authentication tokens in gRPC metadata headers (Bearer tokens, API keys)
- Method names and service identifiers
- Request and response protobuf payloads
- Streaming data (client/server/bidirectional)

**History of WithInsecure() deprecation**: grpc-go v1.35 (released March 2021)
deprecated `grpc.WithInsecure()` and replaced it with `insecure.NewCredentials()`
from `google.golang.org/grpc/credentials/insecure`. The rename was intentional —
the `insecure` package name makes the security choice explicit. However, both the old
and new forms are equally insecure for production traffic.

**Microservice context**: gRPC is primarily used for service-to-service communication.
Many teams assume internal network communication doesn't need TLS because it's "inside
the cluster." However:
- Kubernetes cluster-internal traffic is not encrypted by default
- Container-to-container traffic on the same node bypasses network policies
- Service mesh (Istio, Linkerd) can provide mTLS but is not universally deployed
- Compromised pods can intercept unencrypted gRPC traffic

**Mutual TLS (mTLS)** is the recommended pattern for microservices: both client and
server present certificates, enabling bidirectional authentication. This prevents
a compromised service from accepting connections from unauthorized clients.


## Vulnerable Code

```python
# --- file: vulnerable.go ---
// GO-NET-002 positive test cases — all SHOULD be detected
package main

import "google.golang.org/grpc"

func grpcInsecureClient() {
	addr := "backend:50051"
	// SINK: WithInsecure disables all transport security
	conn, _ := grpc.Dial(addr, grpc.WithInsecure())
	defer conn.Close()
}

func grpcInsecureOption() {
	// SINK: grpc.WithInsecure() as standalone option
	opts := []grpc.DialOption{
		grpc.WithInsecure(),
	}
	conn, _ := grpc.Dial("service:9090", opts...)
	defer conn.Close()
}

# --- file: go.mod ---
module example.com/go-net-002/positive

go 1.24.0

require google.golang.org/grpc v1.80.0

require (
	golang.org/x/net v0.49.0 // indirect
	golang.org/x/sys v0.40.0 // indirect
	golang.org/x/text v0.33.0 // indirect
	google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
	google.golang.org/protobuf v1.36.11 // indirect
)

# --- file: go.sum ---
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
```

## Secure Code

```python
// SECURE: standard TLS with system CA roots
import (
    "crypto/tls"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func connectTLS(addr string) (*grpc.ClientConn, error) {
    tlsConfig := &tls.Config{
        MinVersion: tls.VersionTLS12,
    }
    creds := credentials.NewTLS(tlsConfig)
    return grpc.Dial(addr, grpc.WithTransportCredentials(creds))
}

// SECURE: mutual TLS (mTLS) — client and server both present certificates
func connectMTLS(addr, clientCert, clientKey, caCert string) (*grpc.ClientConn, error) {
    cert, err := tls.LoadX509KeyPair(clientCert, clientKey)
    if err != nil {
        return nil, err
    }
    caCertPool := x509.NewCertPool()
    caCertBytes, err := os.ReadFile(caCert)
    if err != nil {
        return nil, err
    }
    caCertPool.AppendCertsFromPEM(caCertBytes)

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
        MinVersion:   tls.VersionTLS12,
    }
    return grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
}

// ACCEPTABLE: loopback-only with insecure (local testing, same process)
// Only if addr is guaranteed to be "localhost" or "127.0.0.1"
func connectLocalTest(addr string) (*grpc.ClientConn, error) {
    if !strings.HasPrefix(addr, "localhost") && !strings.HasPrefix(addr, "127.0.0.1") {
        return nil, fmt.Errorf("insecure transport only permitted for localhost")
    }
    return grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
}

```

## Detection Rule (Python SDK)

```python
"""GO-NET-002: gRPC client using insecure connection (grpc.WithInsecure/WithNoTLS)."""

from codepathfinder.go_rule import QueryType
from codepathfinder import flows
from codepathfinder.go_decorators import go_rule


class GoGRPC(QueryType):
    fqns = ["google.golang.org/grpc"]
    patterns = ["grpc.*"]
    match_subclasses = False


@go_rule(
    id="GO-NET-002",
    severity="HIGH",
    cwe="CWE-300",
    owasp="A07:2021",
    tags="go,security,grpc,tls,insecure,CWE-300,OWASP-A07",
    message=(
        "Detected gRPC client using grpc.WithInsecure() or grpc.WithNoTLS(). "
        "This creates an unencrypted gRPC connection — all RPC calls, including "
        "authentication metadata (tokens, credentials), are transmitted in cleartext. "
        "Use grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})) instead."
    ),
)
def detect_grpc_insecure_connection():
    """Detect gRPC client connecting without TLS (WithInsecure / WithNoTLS)."""
    return GoGRPC.method("WithInsecure", "WithNoTLS")
```

## How to Fix

- Use grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)) for all production connections.
- For microservice mesh environments, prefer mutual TLS (mTLS) for bidirectional authentication.
- Set tls.Config.MinVersion to tls.VersionTLS12 or tls.VersionTLS13.
- grpc.WithInsecure()/WithNoTLS() is only acceptable for localhost-only connections.
- Consider Istio or Linkerd for cluster-wide mTLS without per-service configuration.
- Use grpc.DialContext with context-based connection management for production.

## Security Implications

- **Auth Token Interception:** gRPC metadata headers carrying Bearer tokens, API keys, or service account
credentials are transmitted in cleartext. Any network observer on the path
can capture these credentials and replay them against the target service.

- **Microservice Impersonation:** Without TLS certificate verification, a compromised service can MITM gRPC calls
between services, reading all payloads and injecting crafted responses.

- **Data Exfiltration:** gRPC streaming calls (often used for bulk data transfer) send their entire
payload in plaintext. An observer can capture complete request/response payloads
without any decryption.

- **Pod-to-Pod Traffic Exposure:** In Kubernetes, all pods on the same node share the same network namespace.
A compromised pod can use tcpdump to capture unencrypted gRPC traffic between
other pods on the same node, bypassing Kubernetes NetworkPolicy.


## References

- [CWE-300: Channel Accessible by Non-Endpoint — MITRE](https://cwe.mitre.org/data/definitions/300.html)
- [gRPC Go authentication documentation](https://grpc.io/docs/guides/auth/)
- [gRPC Go TLS — pkg.go.dev](https://pkg.go.dev/google.golang.org/grpc/credentials)
- [grpc.WithInsecure deprecation note (grpc-go v1.35 changelog)](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-auth-support.md)
- [google.golang.org/grpc/credentials/insecure package](https://pkg.go.dev/google.golang.org/grpc/credentials/insecure)
- [OWASP Transport Layer Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html)

---

Source: https://codepathfinder.dev/registry/golang/security/GO-NET-002
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
