Skip to content

Redact sensitive fields' default:/example: from the exported schema - #75

Merged
VSN2015 merged 1 commit into
masterfrom
fix/redact-sensitive-default-example
Sep 29, 2026
Merged

VSN2015 merged 1 commit into
masterfrom
fix/redact-sensitive-default-example

Conversation

@VSN2015

@VSN2015 VSN2015 commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

The bug

sensitive: true marks a field writeOnly/x-permittable-sensitive in the exported OpenAPI/JSON Schema document and registers it for log redaction, but annotate() in lib/permittable/json_schema.rb still copied that same field's default:/example: into the exported document in cleartext:

optional :ssn, :string, sensitive: true, default: "000-00-0000", example: "078-05-1120"

exported

{"default"=>"000-00-0000", "examples"=>["078-05-1120"], "writeOnly"=>true, "x-permittable-sensitive"=>true}

The exported schema is the one output channel meant to leave the app — client-generator tooling or a public docs endpoint — unlike the request log a sensitive: value is otherwise redacted from. Shipping the real default/example there defeats the point of marking the field sensitive in the first place.

The fix

annotate() now skips the default:/example: assignment entirely when field[:sensitive] is set:

unless field[:sensitive]
  schema["default"] = json_value(field[:default], decimal: decimal_mode) if field.key?(:default)
  schema["examples"] = [json_value(field[:example], decimal: decimal_mode)] if field.key?(:example)
end

Omitting the keys, rather than redacting to a placeholder string, matches how every other untranslatable or opaque fact in this file is already handled — left out, with an x-permittable-* extension (here, writeOnly/x-permittable-sensitive, already present) as the only signal that something is missing. description is untouched: it's authored documentation text, not the value itself. The non-sensitive case (default:/example: with no sensitive:) is unaffected, and the cascade from a sensitive nested/array block onto its children already resolves field[:sensitive] before annotate() runs, so it's covered by the same check with no extra plumbing.

Verification

  • bundle exec rspec spec/json_schema_spec.rb — 64 examples, 0 failures
  • bundle exec rspec (full suite) — 853 examples, 0 failures
  • bundle exec rubocop lib/permittable/json_schema.rb spec/json_schema_spec.rb — no offenses
  • New test in spec/json_schema_spec.rb reproducing the exact scenario from the bug report, confirmed to fail before the implementation change (default/examples present alongside writeOnly) and pass after
  • Updated a pre-existing :json-field test that had asserted the buggy behavior (default/examples exported alongside sensitive: true) — split into a plain-annotation case and a sensitive-omission case

🤖 Generated with Claude Code

sensitive: true already marked a field writeOnly/x-permittable-sensitive
and redacted it from logs, but annotate() still copied that same field's
default:/example: into the exported JSON Schema in cleartext — the one
output channel meant for client-generator tooling or a public docs
endpoint. optional :ssn, :string, sensitive: true, default: "000-00-0000"
exported the real default alongside writeOnly: true.

Skip the default:/example: assignment when field[:sensitive] is set,
consistent with how every other untranslatable or sensitive fact in this
file is handled: omitted, with the x-permittable-* extension as the only
signal. The non-sensitive case is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@VSN2015
VSN2015 merged commit 6568646 into master Sep 29, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant