Skip to content

F5 masking, Gate 1 step 2: key domains and rotation - #215

Merged
SauravYadav12 merged 3 commits into
mainfrom
feat/f5-mask-step2
Nov 6, 2026
Merged

SauravYadav12 merged 3 commits into
mainfrom
feat/f5-mask-step2

Conversation

@SauravYadav12

Copy link
Copy Markdown
Collaborator

Masking (F5), Gate 1 step 2: key domains and rotation (design, section 8). Builds on step 1 (#214).

What it does

  • fox mask key [status] [--json] lists each key domain, its key number, when it was made and rotated and by whom, the tables under it on running branches, and any made under an older key. It never shows a key.

  • fox mask key create <domain> [--yes] makes a domain with its own key. Tables in different domains hash apart, so they can't be joined on masks; joins work within one domain.

  • fox mask key use <domain> --table [schema.]table [--branch b] [--yes] puts a table's masks under a domain. Only that table is re-masked.

  • fox mask key rotate <domain> [--yes]: the preview states the cost first (about 21 s per million rows, every hash changes, no undo). Then, on every running branch, table by table:

    1. a second shadow is kept by the triggers under the new key, alongside the old one;
    2. it is filled in batches;
    3. it is swapped in, in one transaction, with the view replaced in place so grants are kept.

    Masked readers see the old masks until each table is done. Afterwards the new key replaces the old key's file, which deletes the old key.

    • A rotation that was cut short carries on when run again.
    • A branch started during a rotation is re-masked.
    • A branch stopped through a rotation is named in the status, and fox mask setup --branch re-masks it.
  • fox mask key drop <domain> deletes a domain no table uses. A stopped branch blocks the delete only if a table was put under the domain there, or the branch was made after the domain was.

  • Console and API:

    • a Keys panel on the Masking page to make, rotate (behind a dialog showing the cost), delete, and move a table;
    • GET /api/masking/keys and POST /api/masking/key, admins only, documented in openapi.yaml;
    • security log kind mask.key.

Found and fixed by the suite

A write already in flight when a table's masks were swapped could finish with trigger code that named the shadow just renamed away, and fail. The start, swap and repoint steps now briefly hold that table's writes first. Reads are not held. The hold is bounded, and retried if a long transaction is in the way.

Checked on a throwaway PostgreSQL 18 with 200,000 rows and six writers opening a new connection per write: no write refused, and every mask under the new key.

Tests

All in the fox-test VM, on the final code:

  • make integration: 811/811. The new §11aj covers:
    • domains hashing apart, and a table moved to another domain and back;
    • a rotation while the application writes and a masked account reads: every mask under the new key, the old key's file replaced, the next key removed, nothing left over, and the key in no log;
    • a stopped branch named in the status, then re-masked;
    • a domain in use kept, and the default never deleted;
    • the API and the security log.
  • integration-v2: 403/403.
  • integration-ui: 48/48, plus the upgrade test. The new key test makes a domain, moves a table, rotates the key, checks a domain in use is kept, then deletes it.
  • integration-f5: 57/57; integration-f1: 70/70; integration-f6: 71/71.
  • Web unit tests: 256/256.
  • Go unit tests in both editions: domain names, the key records, which stopped branches may use a domain, and the rekey and repoint SQL.
  • go vet for macOS, Linux and Windows.
  • Feature docs and checklist updated; the PDFs were re-rendered.

Steps 3–5 follow: unmask and the audited lookup, the access-path suite, and masked branches.

fox mask key [status|create|rotate|drop|use]: domains whose masks hash apart,
a table's masks put under one, and a key rotated on every running branch
while masked readers read the old masks until each table is done, then the
old key deleted. A Keys panel on the Masking page, /api/masking/keys and
/api/masking/key, every change in the security log.
…main asks only branches that may use it

A write already running when a rotation swapped a table's masks finished with
triggers naming a shadow just renamed away. The start, swap and repoint now
hold the table's writes for a moment (reads go on), bounded and retried.
A stopped branch blocks deleting a domain only if a table was put under it
there or it was made since the domain was. The default key's making time is
its file's.
@vercel

vercel Bot commented Nov 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
foxbyte-get Ready Ready Preview Nov 6, 2026 8:05am UTC

@SauravYadav12
SauravYadav12 merged commit c350720 into main Nov 6, 2026
8 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 6, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant