feat(go,terraform): mask sensitive body values in HTTP logs
Why
Terraform's HTTP debug logging writes raw request and response bodies before responses are decoded, so values marked sensitive (x-speakeasy-param-sensitive or format: password) that the API returns for the first time are not yet known to value-based redaction. API error strings can also contain response bodies.
What changed
- Generator AST:
Operation.SensitiveRequestBodyGraph(),SensitiveResponseBodyGraph()andHasSensitiveBodies()(internal/ast/sensitive_body.go) compute a language-agnostic graph of where sensitive values sit in an operation's bodies, by property name. The graph is built alongTypeDef.Children, andTypeDef.Walkskips bodies with nothing sensitive. They handle objects, error types, arrays, maps andadditionalProperties, unions, recursive schemas, and SSE/JSONL streams (masked as a whole). They are declared intypeDefs.d.ts, so any target can render them. - Go SDK: a public
redactpackage holds the rendered graphs and exposesRequestBody(ctx, contentType, body)/ResponseBody(ctx, contentType, body). Methods of operations with sensitive bodies stamp the operation ID on the request context at request construction (utils.WithOperation), so a transport can find it from the request orres.Request. The transport and serialization are unchanged. Operations without sensitive bodies generate byte-identical code. - Go
APIError.Error()masks the body when the SDK has any sensitive bodies; theBodyfield stays raw. - Terraform: the logging transport and
debugResponsemask request and response bodies throughredact. The existing known-value redaction is kept.
JSON bodies are masked value by value. application/x-www-form-urlencoded fields and multipart parts are matched by name: a sensitive field, or a field repeated for an array of sensitive values, is masked, and a JSON-encoded field or part is masked within. The masked body stays a valid form or multipart body with the same boundary. Other media types, and bodies that cannot be parsed, are masked as a whole for operations that have sensitive values. Unchanged bodies are returned byte-identical. Split request bodies (one schema per content type) are separate methods with their own operation IDs, so each is masked with its own schema.
Testing
internal/astunit tests for the graph: nested, array items, map values beside a declared plain property,format: passwordwith a renamed property, union variants, recursion, error types, streams, and a plain property sharing a sensitive one's name.- New primary fragment
sensitive-body-redaction.yamlwith Go runtime tests (redact_additional_test.go) for JSON, form, multipart and split request bodies sent through the SDK, the context stamp, andAPIError.Error(). The form and multipart tests fail when their masking is disabled.TARGET=primary make test-gopasses. - New Terraform review test checking that the transport log, error strings, and
debugResponsehide computed sensitive values while decoded values stay raw. The full./scripts/build-review-terraform.shacceptance suite passes. go test ./pkg/generate/...(the Python snapshot tests fail onmaintoo, on a mypy_DURATION_ADAPTERannotation error), andmake check-template-{go,terraform,cli}.
Known limitations
- Union variants are matched by property name, so a property sensitive in one variant is masked in all.
- Typed errors without a message field still
json.Marshalthemselves inError(). - Custom hooks that build their own error strings from bodies need to call
redact.ResponseBody. - The CLI keeps its own request-body graph from #91 for now; it could switch to the AST methods.