go-grpc — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-grpc (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A bulleted imperative like {match} tells the agent to never reveal, disclose, or mention something to the user. Used adversarially it can instruct the agent to hide its tool calls or lie about what it did — stripping the transparency a user relies on to trust the agent.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Treat gRPC as a transport. Keep .proto-generated code and business logic separated. The official Go implementation is google.golang.org/grpc; pair it with protoc-gen-go + protoc-gen-go-grpc (or buf generate).
.proto defines the contract; generated code lives in gen/; service implementation lives in internal/. Never edit generated files.string, int32) cannot be evolved without breaking callers.fmt.Errorf becomes codes.Unknown on the wire — the client cannot decide whether to retry.context.Background() to a remote service. Set context.WithTimeout per call.grpc.ClientConn per request is a TLS handshake leak.| Need | Use |
|---|---|
| Define service | .proto file in proto/<service>/v1/ |
| Generate stubs | buf generate or protoc --go_out --go-grpc_out |
| Cross-cutting (auth, logging, recovery) | grpc.ChainUnaryInterceptor / ChainStreamInterceptor |
| Health probes (Kubernetes) | grpc_health_v1 from google.golang.org/grpc/health |
| Errors with details | status.Errorf(codes.X, ...) + WithDetails(errdetails.BadRequest{...}) |
| Tests | google.golang.org/grpc/test/bufconn |
| Service mesh / mTLS | credentials.NewTLS or delegate to Istio/Linkerd |
Read references/proto-and-codegen.md when organizing.protopackages or wiringbuf. Read references/status-and-errors.md when mapping domain errors to gRPC codes.
import (
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
)
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(recoveryUnary, loggingUnary, authUnary),
grpc.ChainStreamInterceptor(recoveryStream, loggingStream),
)
pb.RegisterUserServiceServer(srv, &userService{...})
healthpb.RegisterHealthServer(srv, health.NewServer())
go func() { _ = srv.Serve(lis) }()
// Graceful shutdown bounded by a hard timeout.
<-shutdownSignal
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
srv.Stop()
}Three pieces are non-negotiable: interceptors for cross-cutting concerns, health service for Kubernetes probes, and a bounded graceful shutdown.
conn, _ := grpc.NewClient("dns:///user-service:50051",
grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)),
grpc.WithDefaultServiceConfig(`{
"loadBalancingPolicy": "round_robin",
"methodConfig": [{
"name": [{"service": "user.v1.UserService"}],
"timeout": "5s",
"retryPolicy": {
"maxAttempts": 3, "initialBackoff": "0.1s", "maxBackoff": "1s",
"backoffMultiplier": 2, "retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}`),
)
client := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second); defer cancel()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: id})The service config is the right place for retries — let the library handle the loop, backoff, and UNAVAILABLE-only filter.
A raw Go error returned from an RPC becomes codes.Unknown. The client cannot tell a 404 from a 500. Always use status.Errorf:
if errors.Is(err, ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "user %q not found", req.Id)
}
if errors.As(err, &validationErr) {
st, _ := status.New(codes.InvalidArgument, "validation").WithDetails(
&errdetails.BadRequest{FieldViolations: violations(validationErr)},
)
return nil, st.Err()
}
return nil, status.Errorf(codes.Internal, "lookup: %v", err)Quick map:
| Domain | Code |
|---|---|
| Missing/invalid field | InvalidArgument |
| Not found | NotFound |
| Already exists | AlreadyExists |
| Unauthenticated | Unauthenticated |
| Authenticated but forbidden | PermissionDenied |
| Rate-limited | ResourceExhausted |
| Dependency down, retriable | Unavailable |
| Bug, unexpected | Internal |
| Pattern | Use case |
|---|---|
| Server streaming | Log tailing, paginated result sets, server-sent events |
| Client streaming | File upload, batch ingest |
| Bidirectional | Chat, real-time sync |
Streams must respect ctx.Done(). A goroutine reading from a stream after the client disconnects is a slow leak.
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
for _, u := range s.repo.All(stream.Context()) {
if err := stream.Send(toProto(u)); err != nil {
return err // includes ctx canceled
}
}
return nil
}bufconn is an in-memory net.Listener. It exercises the real gRPC stack — interceptors, marshaling, metadata — without binding a TCP port. See references/testing.md for the full harness plus table-driven status-code assertions, metadata injection, and stream testing.
credentials.PerRPCCredentials to attach a token and validate inside an auth interceptor.| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
return fmt.Errorf("not found") | Wire code is Unknown, clients can't retry-discriminate | status.Errorf(codes.NotFound, ...) |
context.Background() to a client call | No deadline → goroutines pile up on a slow dependency | context.WithTimeout(parent, 5s) |
New ClientConn per request | TLS handshake every call; sockets exhaust | One grpc.NewClient at startup, reuse |
Bare string as RPC argument | Cannot add fields without breaking callers | Always Request/Response messages |
| Reflection on in production | Lets attackers enumerate every method | Compile-out with build tag in prod |
codes.Internal for all errors | Client retry config can't distinguish bugs from outages | Map domain → specific codes |
| No health service | Kubernetes can't gate traffic; rolling deploys break | Register grpc_health_v1 |
Ignoring stream.Context().Done() | Goroutines run after client disconnect | Select on ctx.Done() in stream loops |
.proto packages are versioned (pkg/v1, not pkg)status.Errorf with a specific codecontext.WithTimeoutgrpc_health_v1GracefulStop is bounded by a time.After fallbackbufconn and assert status.Code(err).proto layout, buf.yaml, codegen flagserrdetailsbufconn, metadata, streaming assertions~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.