Skip to content

The three credential stores stage writes for their routes and re-encrypt without a version check #1127

Description

@dpoulopoulos

provider_store_service, search_tool_store_service and guardrail_store_service share one shape, and two review findings on #1120 are really findings about that shape. Both apply to all three equally, which is why neither belongs in a PR that touches only the newest one.

Transaction ownership

src/gateway/AGENTS.md says "Services own commits and rollbacks." These three do not: save_* and delete_* stage mutations and say so in their docstrings ("staged; caller commits"), leaving the route to call db.commit() and to map an IntegrityError to a 409.

There is a reason it ended up this way. The route needs the commit to fail in its own hands so it can turn a primary-key collision into a 409 with a resource-specific message, and it needs the write and the cache refresh that follows to be ordered. But the convention and the code disagree, and right now the code wins three times over. Either the services should own their transactions and hand back something a route can turn into a 409, or the convention should record this as the deliberate exception it has become.

Re-encryption without a version check

reencrypt_* reads every row holding a secret, decrypts, re-encrypts and lets the ORM flush. Nothing pins the write to the ciphertext that was read. A PATCH committing between the read and the flush is overwritten with a re-encryption of the value it replaced.

The window is small and the operation is rare, run by hand during a key rotation. It is also not guarded at all today: with_for_update() is not used on this path, and SQLite would not emit FOR UPDATE if it were. A conditional update matching the original ciphertext, treating zero rows as "someone else got there first", would close it.

Why this was deferred

Raised in review on #1120: #1120 (comment) and #1120 (comment)

Changing one store to answer them would leave three stores with two shapes, which is worse than three stores with one flawed shape: the next person copies whichever they find first. Both points want a single change across all three.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions