Skip to content

Support format: "password" as Redacted in JSON Schema import/export and the OpenAPI generator #10394

Open

Description

@fubhy

Need

Password fields in OpenAPI specs and JSON Schema documents come out as plain string, both from @effect/openapi-generator and from SchemaRepresentation.fromJsonSchemaDocument. That means credentials, API keys and tokens can show up in logs, error messages and traces, and generated schemas have no way to mark them as secrets.

Wrapping them by hand is hard to get right. The httpclient format sends payloads with bodyJsonUnsafe and never encodes them, so if you swap a field to Schema.Redacted, the request carries the literal string "<redacted>". Today the only workaround is a hand-written wrapper module around each affected operation.

Spec

OpenAPI defines password as one of the formats built into the OAS (alongside int32, int64, float and double). It applies to string:

JSON Schema's own format list doesn't include password. There, format is an annotation, so the value carries over unchanged from OpenAPI specs to the JSON Schema documents produced from them.

Proposal

The mapping is format: "password" ↔ Redacted, in both directions:

  • JSON Schema import (fromJsonSchemaDocument): a string with format: "password" becomes Schema.RedactedFromValue(Schema.String). Decoding a JSON value gives a Redacted, encoding gives back the plain string, and checks such as minLength still apply to the inner string. Strings used as record keys or propertyNames stay plain.
  • Code generation (toCodeDocument): print Schema.Redacted(Schema.String) with type Redacted.Redacted<string>. Generated schemas keep Type and Encoded equal and go through toCodecJson for JSON, as HttpApi and generated response decoding already do.
  • JSON Schema export (toJsonSchemaDocument): strings wrapped by Schema.Redacted or Schema.RedactedFromValue emit format: "password", unless the inner schema already sets a format. An HttpApi server's OpenAPI spec would then regenerate into a client with the same Redacted fields.
  • @effect/openapi-generator:
    • httpapi: works as soon as the schemas use Schema.Redacted.
    • httpclient: operations whose params or payload contain a password field encode them through Schema.toCodecJson before sending. Every other operation's output stays the same. Multipart bodies can't go through the JSON codec because of their files, so they unwrap their top-level Redacted fields instead.
    • httpclient-type-only: keeps plain string, since nothing decodes there.

Open questions

  • Default or opt-in? Turning it on by default changes the generated types of existing password fields from string to Redacted<string>. That is a breaking change for those users, so it probably needs a minor release. An option would avoid that, but most users would never find out it exists.
  • Open objects. A StructWithRest index signature also applies to declared keys, so an open object's Record(String, Json) rest rejects the Redacted value when encoding. One fix is to import open objects that contain a password field as a plain Struct: extra properties are still accepted on decode but stripped instead of kept. Is that acceptable, or should the rest exclude declared keys?

I have a working prototype of all of the above, with tests. I'm happy to open a PR once we agree on the direction.

Activity

  1. self-assigned this
    on Nov 5, 2026
  2. fubhy commented on Nov 5, 2026

    @fubhy
    MemberAuthor

    @effect-bot take a shot at this from scratch

  3. effect-bot commented on Nov 5, 2026

    @effect-bot
    Contributor

    Thanks @fubhy. I've queued this as EFF-3409, and Effect Bot will build it from scratch. The PR will link back to this issue and call out the open questions (default vs opt-in, and open objects).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions