Skip to content

bug(secrets): the envelope store allows duplicate (owner_vault, name), so first-time secret creation is not idempotent #17850

Description

@mrveiss

Problem

The envelope secrets store does not enforce a unique (owner_vault, name). autobot-backend/services/envelope_secrets_service.py create() inserts a new Secret row unconditionally, and neither autobot-backend/models/secret.py nor migration 20260614_057_secrets_envelope_store.py declares the pair unique.

So any create that runs twice for the same name mints a second secret:

#17846 closes the #17826 path: the stored id is now carried forward, so a save rotates and does not create. First-time creation is still not idempotent. Raised in review on #17846.

Why it is not fixed caller-side

Making callers idempotent by looking an existing secret up by name and rotating it is unsafe today. On an installation already hit by #17826, a provider has several rows named llm:provider:<name>:api_key, and one of them is the orphaned original key, the only recovery copy (see the recovery note on #17826). A name lookup would pick one of those rows and could rotate a new value over the original.

Fix

  1. Before any constraint can be added, existing same-name duplicates must be resolved. That is a decision about stored credentials, which a human approves under Umbrella: SLM Data hygiene page — detect and safely clean up orphaned data across services #17038; no agent picks the survivor. Blocked on the bug(slm-secrets): saving LLM settings overwrites every provider API key in the vault with its masked display string #17826 recovery being done or explicitly waived per installation.
  2. Then add UNIQUE (owner_vault, name) (migration plus model), and make the system-vault create return the existing secret's id on conflict rather than inserting. This is server-side, so every caller (LLM, SSO, others) gets it at once.

Acceptance criteria

  • Migration adds the unique constraint, and refuses to apply (naming the duplicates) when duplicates exist, rather than choosing one
  • Creating an existing (owner_vault, name) returns the existing id; a test proves two creates yield one row
  • No code path selects among same-name rows by order

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions