Skip to content

feat(models): add sensitiveDisplay to keep personal data out of error messages - #557

Merged
nicola-smartive merged 1 commit into
mainfrom
feat/sensitive-display-field
Aug 31, 2026
Merged

feat(models): add sensitiveDisplay to keep personal data out of error messages#557
nicola-smartive merged 1 commit into
mainfrom
feat/sensitive-display-field

Conversation

@smartive-nicolai

Copy link
Copy Markdown
Contributor

Problem

getTechnicalDisplay identifies an entity in technical error messages by quoting its display value:

User "Jane Doe (jane@example.com)" (0b7e…) is not deleted.

Those messages are returned to the GraphQL client and written to server logs. For a model whose displayField holds a person's name — or, composed, their email address — an invalid delete or restore therefore writes personal data into both. In a centralised logging setup that means names and emails land in log storage and in CI job output.

The id on its own is enough to find the record, so the display value buys nothing here that is worth that cost.

Change

A new optional sensitiveDisplay flag on the entity model. When it is set, getTechnicalDisplay leaves the display value out and identifies the entity by id alone:

User 0b7e… is not deleted.
{
    name: 'User',
    kind: 'entity',
    displayField: 'fullName',
    sensitiveDisplay: true,
    // ...
}

Every message built through getTechnicalDisplay is covered at once rather than only the one that happens to get noticed first — is already deleted, cannot be deleted because it has …, depends on … which cannot be deleted/restored, Can't restore … directly, is not deleted and the not-found message.

Default behaviour is unchanged: without the flag the message is exactly what it is today.

Deliberately out of scope

The flag does not touch fetchDisplay, which fills the toDelete/toUnlink/restricted maps of the delete dry-run payload. That display is rendered to an authorised admin confirming what they are about to delete, which is precisely the case where the name is the point. Redacting it there would break the confirmation UX rather than protect anything, so it stays.

Notes

  • Like displayField, the flag is not inherited by child models (INHERITED_FIELDS covers queriable/listQueriable/creatable/updatable/deletable), so it must be set on every model that declares a sensitive displayField. This is documented.
  • EntityModel picks the flag up through the existing Object.assign(this, omit(definition, …)) in its constructor; no wiring needed.

Verification

  • New unit test tests/unit/technical-display.spec.ts covers the flagged model, the ordinary model, and both fallbacks. It builds its own local Models so no generated-schema snapshot churns.
  • Reverting the guard makes the sensitive case fail with the exact leaking string above, so the test is not vacuous.
  • npm run lint clean, tsc --noEmit clean, full unit suite 157/157.
  • No existing snapshot changes: no test model sets the flag.

… messages

`getTechnicalDisplay` identifies an entity in technical error messages by
quoting its display value:

    User "Jane Doe (jane@example.com)" (0b7e…) is not deleted.

Those messages are returned to the client and written to server logs, so for
a model whose display field holds a person's name — or, composed, their email
address — an invalid delete or restore writes personal data into both.

Add an optional `sensitiveDisplay` flag on the entity model. When set, the
display value is left out and the id alone identifies the entity:

    User 0b7e… is not deleted.

The id is enough to find the record, and every message built through
`getTechnicalDisplay` is covered at once — already-deleted, cannot-be-deleted-
because-it-has, depends-on, cannot-restore-directly and not-found — rather than
only the one that happened to be noticed.

The flag deliberately does not touch `fetchDisplay`, which fills the delete
dry-run payload: that display is shown to an authorized admin confirming what
they are about to delete, where the name is the point.

Like `displayField`, it is not inherited by child models.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nicola-smartive
nicola-smartive marked this pull request as ready for review August 31, 2026 10:53
@nicola-smartive
nicola-smartive merged commit baf312b into main Aug 31, 2026
23 checks passed
@nicola-smartive
nicola-smartive deleted the feat/sensitive-display-field branch August 31, 2026 10:54
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 29.4.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant