enumcheck

command
v1.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 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