Account secrets reach the browser in the accounts wire shape

Table of Contents

This page is a capture in the inbox bucket of the product backlog — a pre-sprint idea, not yet pulled into a sprint as a story.

1. What

The generated account wire shape carries three credential fields to every caller of iam.v1.accounts.list and iam.v1.accounts.get: password_hash, password_salt and totp_secret. The BFF serves those two subjects at /api/accounts and /api/accounts/:key, so the values reach the browser.

2. Why

Found on 2026-09-23 while documenting the IAM user journeys (User Journeys). It breaks the acceptance criterion "no secret field reaches the browser" that the IAM map set, and it is a live defect rather than a design gap.

Evidence:

  1. projects/ores.iam/modeling/ores.iam.account.org declares password_hash, password_salt and totp_secret without :sql_only: true. The same file marks service_password_hash :sql_only: true and says why: it never goes on the wire.
  2. Because those three lack the marker, the generator emits them into the domain type: projects/ores.web/packages/wire-protocol/src/generated/iam/domain/account.ts declares password_hash: string, password_salt: string and totp_secret: string on interface Account.
  3. The responses carry that type whole: ListAccountsResponse.accounts: Account[] and GetAccountResponse.account: Account | null in projects/ores.web/packages/wire-protocol/src/generated/iam/protocol/account_protocol.ts.
  4. projects/ores.web/packages/bff/src/server.ts registers the generated account routes, which return those responses to the browser unchanged.

Scope sweep, 2026-09-23: every entity model under projects/*/modeling/ was checked for fields whose name matches hash, secret, password, token, salt or credential. Only ores.iam.account.org holds real credentials, and exactly three of its fields lack the marker. service_password_hash is correctly excluded, and password_reset_required is a boolean flag rather than a secret.

The hand-written *_messages.org request and reply models are a separate concern: a cleartext password on a save or login request, and the token in a login reply, are intended. Do not "fix" those.

Nothing works around this today. The fields are on the wire now.

The fix has a precedent in the same entity. Mark all three :sql_only: true, as service_password_hash already is, and read them where authentication needs them through the repository rather than through the domain type. Decide separately whether totp_secret belongs in the save_account_request input, where it is legitimately set.

Not to be confused with the journey gap log: the journeys record what the screens need and do not have. This is a defect in what the server already sends.

3. References

  • projects/ores.iam/modeling/ores.iam.account.org — the fields without the marker.
  • projects/ores.web/packages/wire-protocol/src/generated/iam/domain/account.ts — the generated domain type.
  • projects/ores.web/packages/wire-protocol/src/generated/iam/protocol/account_protocol.ts — the responses that carry it.
  • projects/ores.web/packages/bff/src/server.ts — the routes that serve it.

4. See also

Emacs 29.3 (Org mode 9.6.15)