Environment
- go-passbolt-cli: v0.5.0 (via
passbolt --version returns "go-passbolt-cli")
- Passbolt server: CE 5.11.0 (self-hosted)
- Resource type:
password-and-description (v4, type UUID a28a04cd-6f53-518a-967c-9963bf9cec51)
- Operator context: single Passbolt user, sole ACL owner of the resource
Bug reproduction
Running passbolt update resource --id <UUID> --password <NEW_VALUE> on a resource of type password-and-description deterministically returns HTTP 400:
Raw Response: {
"header": {
"status": "error",
"message": "Could not validate resource data.",
"code": 400
},
"body": {
"secrets": {
"1": {
"user_id": {
"secret_unique": "A secret already exists for the given user and resource.",
"secret_revision_unique": "A secret already exists for the given user, resource and secret revision."
}
}
}
}
}
Reproduced 3× on 2026-07-07 with different resource IDs and different password values.
Root cause (verified against upstream passbolt_api source)
The update resource --password code path builds the secrets[] PUT payload with TWO entries for the same user_id:
- entry 0: encrypted password blob
- entry 1: encrypted description blob (re-encrypted from existing description)
The v5 SecretsTable.buildRules() in passbolt_api/src/Model/Table/SecretsTable.php registers isUnique(['user_id', 'resource_id', 'deleted']) under addCreate with allowMultipleNulls=false. Two entries with the same user_id violate this constraint.
The correct wire format for password-and-description (v4) is ONE secrets[] entry per user with a merged JSON {"password":"...","description":"..."} encrypted as a single PGP blob.
References:
passbolt_api/src/Model/Table/SecretsTable.php — isUnique constraint
passbolt_api/src/Model/Table/ResourcesTable.php — isSecretsProvidedRule requires ONE secret per accessing user
Observed workaround (also verified)
Using passbolt update resource --id <UUID> --secret-field password=<NEW_VALUE> instead of --password <NEW_VALUE> succeeds. The --secret-field code path apparently performs a fetch-merge-encrypt round-trip: fetches the existing secret, decrypts it, merges the new field, re-encrypts as one blob, and PUTs one secrets[] entry. This preserves resource ID + audit history.
Suggested fix
Align the update resource --password code path with --secret-field password=: perform the fetch-merge-encrypt round-trip when the resource type is password-and-description (v4) or v5-default (v5-encrypted-metadata), rather than sending separate secret entries for each field.
Impact
Any automation that rotates a password value on password-and-description resources via the CLI hits a hard failure. Current NGIS workaround is delete+recreate (loses resource ID and audit history — invasive) or migration to --secret-field (preferred but not currently documented as the canonical update pattern).
References
- NGIS internal Passbolt Reference Dossier (2026-07-07): section "Executive Summary §1 + §3" documents this bug with the root-cause diagnosis and workaround
- Community forum #7637 (previously reported symptom under different resource type)
Willingness to help
Happy to submit a PR against cmd/updateResource.go that inserts the fetch-merge-encrypt round-trip when the resource type schema has multiple secret fields. Also happy to add a regression test that reproduces the current bug and passes with the fix.
Environment
passbolt --versionreturns "go-passbolt-cli")password-and-description(v4, type UUIDa28a04cd-6f53-518a-967c-9963bf9cec51)Bug reproduction
Running
passbolt update resource --id <UUID> --password <NEW_VALUE>on a resource of typepassword-and-descriptiondeterministically returns HTTP 400:Reproduced 3× on 2026-07-07 with different resource IDs and different password values.
Root cause (verified against upstream
passbolt_apisource)The
update resource --passwordcode path builds thesecrets[]PUT payload with TWO entries for the sameuser_id:The v5
SecretsTable.buildRules()inpassbolt_api/src/Model/Table/SecretsTable.phpregistersisUnique(['user_id', 'resource_id', 'deleted'])underaddCreatewithallowMultipleNulls=false. Two entries with the sameuser_idviolate this constraint.The correct wire format for
password-and-description(v4) is ONEsecrets[]entry per user with a merged JSON{"password":"...","description":"..."}encrypted as a single PGP blob.References:
passbolt_api/src/Model/Table/SecretsTable.php—isUniqueconstraintpassbolt_api/src/Model/Table/ResourcesTable.php—isSecretsProvidedRulerequires ONE secret per accessing userObserved workaround (also verified)
Using
passbolt update resource --id <UUID> --secret-field password=<NEW_VALUE>instead of--password <NEW_VALUE>succeeds. The--secret-fieldcode path apparently performs a fetch-merge-encrypt round-trip: fetches the existing secret, decrypts it, merges the new field, re-encrypts as one blob, and PUTs onesecrets[]entry. This preserves resource ID + audit history.Suggested fix
Align the
update resource --passwordcode path with--secret-field password=: perform the fetch-merge-encrypt round-trip when the resource type ispassword-and-description(v4) orv5-default(v5-encrypted-metadata), rather than sending separate secret entries for each field.Impact
Any automation that rotates a password value on
password-and-descriptionresources via the CLI hits a hard failure. Current NGIS workaround is delete+recreate (loses resource ID and audit history — invasive) or migration to--secret-field(preferred but not currently documented as the canonical update pattern).References
Willingness to help
Happy to submit a PR against
cmd/updateResource.gothat inserts the fetch-merge-encrypt round-trip when the resource type schema has multiple secret fields. Also happy to add a regression test that reproduces the current bug and passes with the fix.