Help us shape the future of Go! Take the 2026 Go Developer Survey

enumcheck

command
v1.8.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 20, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Command enumcheck finds values gopherstack emits into a wire response field whose real pinned aws-sdk-go-v2 type is a named string enum, but that are not members of that enum's real declared value set -- gopherstack-6flj's guardduty class (GetUsageStatistics.sumByDataSource emitting DetectorFeature names like "S3_DATA_EVENTS" under a field whose real type is the unrelated six-member types.DataSource enum). A typed client decodes this without error -- no key check and no shape check can see it, only comparing the emitted VALUE against the target enum's real declared members does.

GROUND TRUTH, not a naming guess. For each services/<dir>, the pinned aws-sdk-go-v2/service/<mod>@<ver> module is resolved from that service's own import paths (go/ast) cross-referenced against go.mod (golang.org/x/mod/modfile, same approach as cmd/checkpins). Three sources feed enumRegistry:

  • types/enums.go: every `type X string` with a `const ( XFoo X = "FOO"; ... )` block gives X's real declared member set (loadEnumRegistry).
  • api_op_*.go and types/types.go, via cmd/internal/sdkshape (shared with cmd/structfielddiff): every real struct's own field name and declared Go type -- enumRegistry.sdkFieldTypes. The SDK's generated code carries no json tags at all, so for a JSON-family protocol the wire name IS the Go field name.
  • deserializers.go: a `case "wireKey": ... sv.Field = types.SomeEnum(jtv)` structural match (no name-guessing) still backs the bare-key candidate set the map[string]any/index-assignment paths use, and confidentModuleOK's cross-module native-module check (gopherstack-7fps: services/ec2 imports both ec2 and outposts; a wire key's only candidate coming from the non-native module is refused). query/ EC2-query/REST-XML protocols use an xml.Decoder with no such switch, so this resolves zero wire keys for them -- same disclosed scope as cmd/keycheck.

gopherstack-cpztm found the ORIGINAL design's flaw: it compared an emitted value against every enum SDK-wide that happened to share the bare wire key ("status", "type", ...), rather than resolving the emitting Go field to the ONE real SDK member it actually maps to -- 104 eks/sagemaker/glue findings, 102 false. Fixed by resolving precisely wherever a Go field exists to resolve through:

  • Named-struct-literal position (checkStructResponsesInFunc / resolveStructField, structresp.go): the emitting field's wire name (its own json/xml tag, or the Go name itself) is looked up directly in enumRegistry.sdkFieldTypes for the real same-named SDK type, expanded one hop through a directly nested field's own type (amplify's Job -> JobSummary shape) when absent on the type itself (enumRegistry.resolveRealField). Four outcomes, never a candidate set: the real type is unknown (kindUnresolved), the field doesn't exist on it even one hop away (kindPhantomField -- gopherstack-7fps: cloudtrail's Event.EventCategory, sagemaker's PipelineExecutionStep.StepType, both matched to an unrelated real operation's enum before this fix), the field is real but not enum-typed (a plain *string -- ItemError.Code, DevEndpoint.Status, BatchDescribeModelPackageError.ErrorCode -- never checked), or the field resolves to exactly one real enum (checked, confident).
  • map[string]any composite-literal entry (checkLiteralElt), and an `out["wireKey"] = value` index-assignment onto an already-built map (checkIndexAssignsInFunc, gopherstack-3dzb) have no Go field to resolve through at all, so they still fall back to the bare-key candidate set (evalKeyValue): a single real SDK-wide candidate is still confident (sound by elimination), but 2+ candidates (or a Polymorphic plain-string sighting elsewhere) can no longer be reported as a needs-review finding -- which sense applies here is unknown, so a value failing at least one candidate lands in the UNRESOLVED bucket instead (kindUnresolved), never an accusation.
  • cross-enum-reuse (checkCrossEnumReuse, reuse.go) is unaffected: it never compares an actual value against any enum at all, only flags the STRUCTURE of one value source feeding two wire keys whose real SDK enums declare different member sets -- never confident, but not part of the bare-key false-positive class either.

Every value position resolves the same single-hop way: a literal, a same-package const, a types.SomeEnumMember/types.SomeEnum("x") selector/conversion, or a `structVar.Field` read of a field this same function assigned exactly once (localFieldConsts, gopherstack-3dzb), keyed by the (local variable, field name) pair so two different local structs sharing a field name never collide. The struct-literal position separately resolves a Go field to its real WIRE NAME via its own `json` tag, falling back to `xml`, falling back to the Go name itself -- this repo's structs routinely tag a field under a different name (e.g. Go field StatementID tagged json:"StatementId" in services/lambda). A field tagged `json:"-"` is excluded outright; an unkeyed (positional) literal element is skipped. Identity throughout is the (struct TYPE, field) pair, never a bare field name.

SCOPE, disclosed rather than silently under-covered: only files directly in services/<dir> are scanned (no recursion). Local value resolution (including the struct-field hop) is a single hop each -- a value assembled through more indirection resolves to nothing and produces no finding, never a wrong one (gopherstack-3dzb's own recommendation after two auditors hit ~85% false positives attempting full dataflow). resolveRealField's own nested-type expansion is likewise one hop only: this does NOT follow AWS's convention where a List operation's summary type carries a "Summary"/"Detail" suffix the full type lacks (confirmed live: securityhub's real ConfigurationPolicyAssociationSummary has AssociationStatus/AssociationType, but gopherstack's local ConfigurationPolicyAssociation -- matched against the real, smaller, same-named type -- resolves to unresolved or phantom instead; same shape for swf's ActivityType/WorkflowType).

Usage:

go run ./cmd/enumcheck                    # report to stdout, every service
go run ./cmd/enumcheck -dir eks            # limit to services/eks
go run ./cmd/enumcheck -json out.json      # also write the full finding list as JSON

Exit codes: 0 no confident findings (needs-review/unresolved hits may still print), 1 a run error, 2 at least one confident finding.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL