Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/26603-setting-audit-hash-description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@objectstack/platform-objects': patch
---

fix(platform-objects): the settings audit `old_hash` / `new_hash` columns describe both digest formats they carry

Clause-②: no
Metadata-protocol: none

`sys_setting_audit` described `old_hash` and `new_hash` as "SHA-256 of the … value (canonicalised)", and `sys_platform_setting_audit` named no format. A secret row never carries an unkeyed SHA-256, so an operator or agent checking a row against the declared format read every secret row as malformed. Both objects now state what the settings audit writer records:

- **A non-secret value:** `sha256:` followed by the hex SHA-256 of its canonical JSON.
- **A secret value (`encrypted: true`):** the crypto provider's keyed digest, `hmac-sha256:…`. Never an unkeyed hash, which would let anyone holding the trail confirm a guessed secret.
- **Null:** `old_hash` when the previous value was unset, `new_hash` on reset, and either one for a secret when no keyed digest is available.

Rows written before the `sha256:` digest may carry `fnv32:`; compare only values with the same prefix. Only the description text changes: no column type, length or name, and nothing about what is written.
Original file line number Diff line number Diff line change
Expand Up @@ -3457,11 +3457,11 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
},
old_hash: {
label: "Old Hash",
help: "SHA-256 of the previous value (canonicalised). Null when previously unset."
help: "Digest of the previous value, never the plaintext: sha256: hex for a plain value, the keyed hmac-sha256: digest for a secret. Null when previously unset, or for a secret with no keyed digest."
},
new_hash: {
label: "New Hash",
help: "SHA-256 of the new value (canonicalised). Null on reset."
help: "Digest of the new value, never the plaintext: sha256: hex for a plain value, the keyed hmac-sha256: digest for a secret. Null on reset, or for a secret with no keyed digest."
},
encrypted: {
label: "Encrypted",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3457,11 +3457,11 @@ export const esESObjects: NonNullable<TranslationData['objects']> = {
},
old_hash: {
label: "Hash anterior",
help: "SHA-256 del valor anterior (canonicalizado). Null cuando antes no estaba establecido."
help: "Resumen del valor anterior, nunca el texto plano: sha256: en hexadecimal para un valor normal, el resumen con clave hmac-sha256: para un valor secreto. Null cuando antes no estaba establecido, o para un valor secreto sin resumen con clave."
},
new_hash: {
label: "Hash nuevo",
help: "SHA-256 del valor nuevo (canonicalizado). Null al restablecer."
help: "Resumen del valor nuevo, nunca el texto plano: sha256: en hexadecimal para un valor normal, el resumen con clave hmac-sha256: para un valor secreto. Null al restablecer, o para un valor secreto sin resumen con clave."
},
encrypted: {
label: "Cifrado",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3457,11 +3457,11 @@ export const jaJPObjects: NonNullable<TranslationData['objects']> = {
},
old_hash: {
label: "変更前ハッシュ",
help: "変更前の値の SHA-256(正規化済み)。未設定の場合は null。"
help: "変更前の値のダイジェスト(平文ではない):通常の値は sha256: の16進ダイジェスト、機密値は鍵付きの hmac-sha256: ダイジェスト。未設定の場合、または鍵付きダイジェストを得られない機密値の場合は null。"
},
new_hash: {
label: "変更後ハッシュ",
help: "新しい値の SHA-256(正規化済み)。リセット時は null。"
help: "新しい値のダイジェスト(平文ではない):通常の値は sha256: の16進ダイジェスト、機密値は鍵付きの hmac-sha256: ダイジェスト。リセット時、または鍵付きダイジェストを得られない機密値の場合は null。"
},
encrypted: {
label: "暗号化",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3457,11 +3457,11 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
},
old_hash: {
label: "旧哈希",
help: "旧值(规范化后)的 SHA-256。此前未设置时为 Null。"
help: "旧值的摘要,绝不是明文:普通值为 sha256: 十六进制摘要,机密值为带密钥的 hmac-sha256: 摘要。此前未设置时为 Null,机密值没有可用的带密钥摘要时也为 Null。"
},
new_hash: {
label: "新哈希",
help: "新值(规范化后)的 SHA-256。重置时为 Null。"
help: "新值的摘要,绝不是明文:普通值为 sha256: 十六进制摘要,机密值为带密钥的 hmac-sha256: 摘要。重置时为 Null,机密值没有可用的带密钥摘要时也为 Null。"
},
encrypted: {
label: "已加密",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,26 @@ export const SysPlatformSettingAudit = ObjectSchema.create({
description: 'Free-text justification provided by the actor (optional).',
}),

/**
* Same formats as `sys_setting_audit.old_hash`: `sha256:` + hex SHA-256
* of the canonical JSON for a non-secret value, the crypto provider's keyed
* `hmac-sha256:` digest for a secret (never an unkeyed hash). Null when
* previously unset, and null for a secret when no keyed digest is
* available. Older rows may carry `fnv32:`; compare only same-prefix values.
*/
old_hash: Field.text({
label: 'Old Hash',
readonly: true,
maxLength: 128,
description: 'Digest of the previous value. Never the plaintext. Null when previously unset.',
description: 'Digest of the previous value, never the plaintext: sha256: hex for a plain value, the keyed hmac-sha256: digest for a secret. Null when previously unset, or for a secret with no keyed digest.',
}),

/** Same formats as `old_hash`. Null on `reset`, and for a secret with no keyed digest. */
new_hash: Field.text({
label: 'New Hash',
readonly: true,
maxLength: 128,
description: 'Digest of the new value. Never the plaintext. Null on reset.',
description: 'Digest of the new value, never the plaintext: sha256: hex for a plain value, the keyed hmac-sha256: digest for a secret. Null on reset, or for a secret with no keyed digest.',
}),

encrypted: Field.boolean({
Expand Down
21 changes: 16 additions & 5 deletions packages/platform-objects/src/system/sys-setting-audit.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,22 +137,33 @@ export const SysSettingAudit = ObjectSchema.create({
/**
* Content digest of the previous value. Never the plaintext —
* lets operators detect duplicate writes without leaking secrets.
* Format: hex SHA-256 of the canonicalised JSON, or null when
* the previous value was unset.
* The format depends on the value:
* - a non-secret value: `sha256:` + hex SHA-256 of its canonical JSON;
* - a secret value (`encrypted: true`): the crypto provider's keyed
* digest, `hmac-sha256:…`. Never an unkeyed hash: one of a short
* secret lets anyone holding the trail confirm a guess.
* Null when the previous value was unset, and null for a secret when
* no keyed digest is available. Rows written before the SHA-256 digest
* (#26465) may carry `fnv32:`; compare only values with the same prefix.
*/
old_hash: Field.text({
label: 'Old Hash',
readonly: true,
maxLength: 128,
description: 'SHA-256 of the previous value (canonicalised). Null when previously unset.',
description: 'Digest of the previous value, never the plaintext: sha256: hex for a plain value, the keyed hmac-sha256: digest for a secret. Null when previously unset, or for a secret with no keyed digest.',
}),

/** Content digest of the new value. Null on `reset`. */
/**
* Content digest of the new value, in the same formats as `old_hash`
* (`sha256:` for a non-secret value, the keyed `hmac-sha256:` digest
* for a secret). Null on `reset`, and null for a secret when no keyed
* digest is available.
*/
new_hash: Field.text({
label: 'New Hash',
readonly: true,
maxLength: 128,
description: 'SHA-256 of the new value (canonicalised). Null on reset.',
description: 'Digest of the new value, never the plaintext: sha256: hex for a plain value, the keyed hmac-sha256: digest for a secret. Null on reset, or for a secret with no keyed digest.',
}),

/** True when the field is encrypted — flags secret rotation events. */
Expand Down
Loading