|
20 | 20 | * it is still an exposure, and the REST response is the one surface that should |
21 | 21 | * never carry the cleartext. |
22 | 22 | * |
23 | | - * The mask shape is NOT invented here — it mirrors the encrypted-**field** |
24 | | - * convention ADR-0100 pins for `secret` / `password` columns on the generic CRUD |
25 | | - * path (`SECRET_MASK` in `@objectstack/objectql`, exercised by the |
| 23 | + * The mask shape is NOT invented here — it IS the encrypted-**field** convention |
| 24 | + * ADR-0100 pins for `secret` / `password` columns on the generic CRUD path |
| 25 | + * (`SECRET_MASK`, declared in `@objectstack/spec` and re-exported by |
| 26 | + * `@objectstack/objectql`, exercised by the |
26 | 27 | * `records-forms.encrypted-field-behavior` checklist item): |
27 | 28 | * |
28 | 29 | * - **read**: a set value becomes the mask; an unset one stays `null`, so the |
|
33 | 34 | * key is DROPPED from the patch, so a form round-trip that echoes the mask |
34 | 35 | * does not overwrite the stored secret with the mask's literal text. |
35 | 36 | * |
36 | | - * The constant is redeclared rather than imported because this service is |
37 | | - * deliberately framework-agnostic (see the `settings-service.ts` header): it |
38 | | - * defines its own minimal `SettingsEngine` instead of importing `IDataEngine`, |
39 | | - * and does not depend on `@objectstack/objectql` at all. Taking a runtime |
40 | | - * dependency on the whole data engine to reach one string would undo that. The |
41 | | - * long-term fix is to hoist the mask into a package both sides already depend on |
42 | | - * (`@objectstack/spec`) and have objectql re-export it — recorded on #7522 as |
43 | | - * follow-up rather than done here, since it is a cross-package move on a |
44 | | - * security card. |
| 37 | + * The mask itself is now IMPORTED, not redeclared (#7572). #7522 shipped a |
| 38 | + * second byte-identical literal here and said why: this service is deliberately |
| 39 | + * framework-agnostic (see the `settings-service.ts` header) — it defines its own |
| 40 | + * minimal `SettingsEngine` instead of importing `IDataEngine`, and does not |
| 41 | + * depend on `@objectstack/objectql` at all, so taking a runtime dependency on |
| 42 | + * the whole data engine to reach one string was not worth it. That reasoning |
| 43 | + * held against depending on **objectql**, and it still does. It does not apply |
| 44 | + * to `@objectstack/spec`, which is where the mask now lives: spec is already a |
| 45 | + * dependency of this package, and already in its runtime graph (`manifest.ts` |
| 46 | + * → `@objectstack/platform-objects/system` → `@objectstack/spec/data`). The |
| 47 | + * framework-agnostic property is untouched — no objectql import was added here |
| 48 | + * or anywhere in this package. |
45 | 49 | */ |
46 | 50 |
|
| 51 | +import { SECRET_MASK } from '@objectstack/spec/data'; |
47 | 52 | import type { ResolvedSettingValue } from '@objectstack/spec/system'; |
48 | 53 |
|
49 | 54 | /** |
50 | 55 | * Value served in place of a set secret on the REST read path. Says "a secret |
51 | 56 | * is set" without leaking its cleartext; an unset secret resolves to `null` |
52 | 57 | * instead, so set-vs-unset stays observable. |
53 | 58 | * |
54 | | - * Byte-identical to `SECRET_MASK` in `@objectstack/objectql` (ADR-0100) — eight |
55 | | - * U+2022 BULLET characters — so one client-side comparison recognises a masked |
56 | | - * read from either surface. Spelled as the literal, not an escape, so a grep for |
57 | | - * the mask finds both declarations. |
| 59 | + * The ADR-0100 credential read mask under the name this package publishes — the |
| 60 | + * SAME declaration objectql re-exports as `SECRET_MASK`, not a copy of it |
| 61 | + * (#7572), so one client-side comparison recognises a masked read from either |
| 62 | + * surface and no edit can leave the two disagreeing. The literal, its eight |
| 63 | + * U+2022 BULLET characters and its grep-findable spelling are pinned at the |
| 64 | + * declaration in `@objectstack/spec` (`data/secret-mask.ts`). |
58 | 65 | */ |
59 | | -export const SETTINGS_SECRET_MASK = '••••••••'; |
| 66 | +export const SETTINGS_SECRET_MASK = SECRET_MASK; |
60 | 67 |
|
61 | 68 | /** Mask one resolved value: the effective value AND every cascade entry. */ |
62 | 69 | function maskResolved(resolved: ResolvedSettingValue): ResolvedSettingValue { |
|
0 commit comments