Skip to content

feat(go,terraform): mask sensitive body values in HTTP logs - #72

Merged
ThomasRooney merged 15 commits into
mainfrom
fix/terraform-sensitive-response-logs
Oct 8, 2026
Merged

ThomasRooney merged 15 commits into
mainfrom
fix/terraform-sensitive-response-logs

Conversation

@ThomasRooney

@ThomasRooney ThomasRooney commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

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() and HasSensitiveBodies() (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 along TypeDef.Children, and TypeDef.Walk skips bodies with nothing sensitive. They handle objects, error types, arrays, maps and additionalProperties, unions, recursive schemas, and SSE/JSONL streams (masked as a whole). They are declared in typeDefs.d.ts, so any target can render them.
  • Go SDK: a public redact package holds the rendered graphs and exposes RequestBody(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 or res.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; the Body field stays raw.
  • Terraform: the logging transport and debugResponse mask request and response bodies through redact. 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/ast unit tests for the graph: nested, array items, map values beside a declared plain property, format: password with 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.yaml with Go runtime tests (redact_additional_test.go) for JSON, form, multipart and split request bodies sent through the SDK, the context stamp, and APIError.Error(). The form and multipart tests fail when their masking is disabled. TARGET=primary make test-go passes.
  • New Terraform review test checking that the transport log, error strings, and debugResponse hide computed sensitive values while decoded values stay raw. The full ./scripts/build-review-terraform.sh acceptance suite passes.
  • go test ./pkg/generate/... (the Python snapshot tests fail on main too, on a mypy _DURATION_ADAPTER annotation error), and make 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.Marshal themselves in Error().
  • Custom hooks that build their own error strings from bodies need to call redact.ResponseBody.
  • The CLI keeps its own request-body graph from fix(cli): redact body values marked sensitive in diagnostics #91 for now; it could switch to the AST methods.

@ThomasRooney
ThomasRooney requested a review from a team as a code owner October 5, 2026 12:31
@ThomasRooney
ThomasRooney requested a review from 2ynn October 5, 2026 12:31
@github-actions github-actions Bot added the terraform Trigger (12) snapshot tests for terraform label Oct 5, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 20 files

Confidence score: 4/5

  • In sensitive_values_test.go, the failure-path assertion would miss diagnostic leaks of the nested or merged secrets. Check all three values in resp.Diagnostics too.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="zSDKs/terraform-provider-testing/internal/provider/sensitive_values_test.go">

<violation number="1" location="zSDKs/terraform-provider-testing/internal/provider/sensitive_values_test.go:335">
P2: The failure-path assertion checks all three values in logs but only `computed-response-secret` in diagnostics, so diagnostic leaks of the nested or merged secret pass. Check all three values in `resp.Diagnostics` too.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.
Click Fix with cubic on a comment, or reply @cubic-dev-ai fix this, and cubic writes the fix.

Fix all with cubic | Re-trigger cubic

Comment thread zSDKs/terraform-provider-testing/internal/provider/sensitive_values_test.go Outdated
@ThomasRooney
ThomasRooney force-pushed the fix/terraform-sensitive-response-logs branch from fa1d50d to 9827741 Compare October 7, 2026 10:28
@ThomasRooney ThomasRooney changed the title fix(terraform): redact sensitive HTTP response values feat(go,terraform): add masked SDK model logging Oct 7, 2026
@speakeasy-generator-bot

speakeasy-generator-bot Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Generator Snapshot Testing

Selector(s) Result Summary
snapshot-terraform ✅ Snapshot run completed

Snapshot results: https://github.com/speakeasy-api/openapi-generation-snapshots/issues/110#issuecomment-6068136117

Snapshot results are summarized above. Additional run context is linked for maintainers with access.

@ThomasRooney
ThomasRooney force-pushed the fix/terraform-sensitive-response-logs branch from 9827741 to 9a34bce Compare October 7, 2026 11:24

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.
Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Turn on auto-fix | Re-trigger cubic

Comment thread templates/templates/common/go/sanitization.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 4 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.

Turn on auto-fix | Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval skipped because cubic reviewed only this push, not the earlier force-push. Comment @cubic review to review the whole pull request.

View guided diff | Turn on auto-fix | Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 2 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval skipped because cubic reviewed only this push, not the earlier force-push. Comment @cubic review to review the whole pull request.

View guided diff | Turn on auto-fix | Re-trigger cubic

Values marked x-speakeasy-param-sensitive or format: password in request
and response bodies could appear in HTTP debug logs and error messages.
Value-based redaction only covers values known before the request, so a
newly returned secret was logged before it could be recognised.

Generate a per-operation schema graph of sensitive request and response
body locations into a new public redact package. Operations with
sensitive values tag their request context with the operation ID, so
transports and custom HTTP clients can mask bodies by schema path.
APIError messages and the Terraform provider's logging transport and
diagnostics mask bodies with it. Methods of operations without sensitive
values are unchanged.
Move the schema walk from the Go templates into Operation methods
(SensitiveRequestBodyGraph, SensitiveResponseBodyGraph, HasSensitiveBodies)
so that any target can render the same language-agnostic graph. The Go
templates now only render it.
@ThomasRooney
ThomasRooney force-pushed the fix/terraform-sensitive-response-logs branch from b072157 to 0938bf5 Compare October 8, 2026 16:59
@ThomasRooney ThomasRooney changed the title feat(go,terraform): add masked SDK model logging feat(go,terraform): mask sensitive body values in HTTP logs Oct 8, 2026
The redact package now takes the body's Content-Type and masks
application/x-www-form-urlencoded fields and multipart parts by name,
including JSON-encoded fields, keeping the body valid. The sensitive body
graph is now built from TypeDef.Children, with TypeDef.Walk skipping
schemas that hold nothing sensitive.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 14 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread zSDKs/sdk-go/redact/redact.go Outdated
Comment thread zSDKs/sdk-cli/internal/sdk/redact/redact.go Outdated
Comment thread zSDKs/terraform-provider-testing/internal/sdk/redact/redact.go
Comment thread zSDKs/sdk-cli/internal/sdk/redact/redact.go Outdated
Comment thread tests/specs/fragments/primary/sensitive-body-redaction.yaml
…arts

A JSON value whose shape cannot hold the sensitive values of its schema is
masked as a whole. Only multipart/form-data bodies are masked part by part,
since other multipart subtypes have no part names, and masked parts drop
their Content-Transfer-Encoding.
@ThomasRooney
ThomasRooney enabled auto-merge October 8, 2026 17:46

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

@ThomasRooney
ThomasRooney disabled auto-merge October 8, 2026 17:51
ThomasRooney and others added 2 commits October 8, 2026 19:00
The sensitive body graph pruned union members that hold nothing
sensitive, so a value matching such a member looked like it did not
match its schema and was masked as a whole. Union nodes now record that
a plain member exists, and the redactor leaves values of an unexpected
shape readable when one does.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 13 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 1 unresolved issue from previous reviews.

View guided diff | Turn on auto-fix | Re-trigger cubic

Union nodes now record the shapes (object, array, scalar) of members
that hold nothing sensitive, instead of a single flag. A value that
matches no sensitive member stays readable only when a plain member of
the same shape exists.

Form fields and multipart parts are parsed as JSON before the repeated
array item rule applies, so a valid plain object beside a sensitive
array stays readable. Text that is not JSON stays readable only when it
is not a sensitive array item and a plain scalar member exists;
malformed JSON is otherwise masked as before.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 12 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread internal/ast/sensitive_body.go Outdated
Comment thread zSDKs/sdk-go/redact/redact.go Outdated
Plain union members are now recorded by JSON type (object, array,
string, number, boolean), with enums following their underlying type.
A form field or multipart part that is not valid JSON stays readable
only beside a plain string member, of the field or of its items when
sent as repeated fields; a plain integer or boolean member no longer
lets malformed JSON through. Text starting like a JSON object or array
is never accepted as a plain string. JSON scalars must match a plain
member of their own type.

Nested plain unions are followed once each, so recursive unions
terminate. Masked multipart parts also drop Content-Length and
Content-Encoding, which no longer describe the masked payload.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 13 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread internal/ast/sensitive_body.go Outdated
@2ynn

2ynn commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

[P1] Preserve literal field names ending in [] when redacting form and multipart bodies

Both redactForm and redactMultipart unconditionally strip [] before looking up the schema field. A schema can legitimately declare a property whose literal name ends in []:

type: object
properties:
  payload[]:
    type: string
    x-speakeasy-param-sensitive: true
  name:
    type: string

The SDK serializer preserves payload[], and the graph records that exact wire name. Looking up payload instead finds no sensitive edge, so the value is returned unchanged. Reproduced with the current redactor and CLI preview functions:

Form preview:      payload%5B%5D=canary-brackets&name=visible-name
Multipart preview: payload[]: canary-brackets
JSON preview:      {"name":"visible-name","payload[]":"(sensitive)"}

This leaks a schema-marked value with valid input; the generic property name also avoids the CLI's name-based redaction rules.

Please resolve literal schema wire names before applying array-suffix aliases, distinguishing a literal suffix from one appended by the legacy multipart array serializer. Add form and multipart regression cases for a marked scalar named payload[], preserving an unmarked sibling, and retain coverage for arrays serialized with an appended [] suffix.

Form fields and multipart parts were looked up with any trailing []
removed, so a property whose wire name ends in brackets missed its
sensitivity marker and was logged as sent. The exact name is now
looked up first; the suffix is only removed when no schema declares
the bracketed name, as for arrays sent as repeated fields.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread templates/templates/go/auxiliary/{{getRedactLocation}}/redact.go.stmpl Outdated
Comment thread zSDKs/sdk-go/redact/redact.go Outdated
Whether a form field or multipart part name ending in [] named a
literal property or an array sent as repeated fields was decided
across all schemas at once, and only from properties that survived
pruning of the sensitive graph. A literal name in one response schema
could hide a sensitive array of another, and a plain bracketed
property beside a sensitive one was masked.

The graph now lists every declared property of the object nodes it
keeps, and each schema and union variant resolves the exact name, then
the name without the suffix, then its additional properties.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 12 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval skipped because cubic reviewed only this push, not the earlier force-push. Comment @cubic review to review the whole pull request.

View guided diff | Turn on auto-fix | Re-trigger cubic

…n redacting

- JSON with a repeated object key is masked as a whole: decoding keeps
  only the last value, so the bytes returned unchanged could still hold
  an earlier sensitive one.
- Multipart parts of a field with sensitive values are masked as a whole
  when transfer or content encoded, since their bytes are not the value.
- Every repeated item of a sensitive array is masked, including `null`.
- A form body whose keys start like JSON is masked as a whole.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread templates/templates/go/auxiliary/{{getRedactLocation}}/redact.go.stmpl Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 8 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval skipped because cubic reviewed only this push, not the earlier force-push. Comment @cubic review to review the whole pull request.

View guided diff | Turn on auto-fix | Re-trigger cubic

@2ynn

2ynn commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Agent reply: re literal field names ending in []

Fixed in 0bf0439 and 7fec620:

  • Form fields and multipart parts are now resolved per schema and union variant: the exact wire name first, then the name without a trailing [] (arrays sent as repeated fields), then additional properties.
  • The sensitive graph lists every declared property of the object nodes it keeps, so a literal payload[] is matched as itself and a plain token[] beside a sensitive token stays readable.

Coverage:

  • TestRedactFormRequestBody / TestRedactMultipartRequestBody: a marked payload[] is masked in form and multipart bodies with its unmarked siblings readable; a plain token[] stays readable beside a sensitive token; arrays sent as tokens[]=… are still masked; a plain tokens[] declared by one response schema does not hide the sensitive tokens array of another.
  • CLI diagnostics (fix(cli): redact body values marked sensitive in diagnostics #91): form and multipart previews hide a marked payload[] in dry-run, JSON dry-run and debug.

A form field or multipart part that is not a JSON object or array may be
one item of an array sent as repeated fields, so it is now matched
against the item schemas too: integer and boolean items of a union with
a sensitive object stay readable, as string items already did.

Cases for property names ending in brackets move to dedicated
operations tested only by the Go SDK, since other targets do not
generate them yet. The declared-fields test preallocates its slice.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 10 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would not auto-approve. Auto-approval skipped because cubic reviewed only this push, not the earlier force-push. Comment @cubic review to review the whole pull request.

View guided diff | Turn on auto-fix | Re-trigger cubic

@ThomasRooney
ThomasRooney added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit a2f7740 Oct 8, 2026
240 checks passed
@ThomasRooney
ThomasRooney deleted the fix/terraform-sensitive-response-logs branch October 8, 2026 20:34
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

terraform Trigger (12) snapshot tests for terraform

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants