secrets.sgit.ai

A zero-knowledge secrets manager that will run entirely in the browser. The site is static on GitHub Pages. The only cloud is one GCP project per environment, holding Identity Platform for login and a Cloud Storage bucket for ciphertext. The browser does every cryptographic operation. A full compromise of the GCP project, the Identity Platform admin or the bucket yields ciphertext and login metadata, never a secret.

What exists at this version. The pipeline, the live site, and the content pages that describe the design. No app, no admin, no probe page is built. Every row below marked proposed is a design, not a thing. The design is in the brief; what is real is in /shipped/ and docs/reality.md, generated from the same data as the table on this page.

The three-step demo, when it exists

Status, from /shipped/: proposed Sign in and out with Google and email/password against the chosen environment · proposed Passkey with WebAuthn PRF derives the keyring wrapping key; RP ID secrets.sgit.ai · proposed Entries: six kinds kept apart, vault list, entry page, copy and reveal, lock timers

  1. Sign in with Google or an email address, against the environment shown in the header.
  2. Touch your passkey. The authenticator returns a secret bound to this origin; the browser derives the key that opens your keyring.
  3. See your secret. It was ciphertext in a bucket a second ago and it is plaintext only in this tab, until you lock, sign out or leave.

None of the three steps is built. How it works draws the flows; the keyring page is the file format; security is what each party gets and what the design cannot withhold.

What it will be

A password-manager-shaped app where the "passwords" can be anything small and secret: passwords, API keys, sgit vault keys and read keys, PKI private keys, short notes. Unlocked by a passkey using the WebAuthn PRF extension, with a recovery code as the second unlock method. Stored as an encrypted keyring in a bucket the user's login can reach. Readable by no one else, including the people who run the bucket.

Three principles are not negotiable: plaintext exists only in the browser, briefly, after a passkey gesture; the login decides which paths you may touch and the passkey decides whether the bytes mean anything; and nothing in the repository is secret, so the whole configuration of every environment is public.

What a compromised party would get

The design's threat table, from section 3.4 of the brief, in full on the security page. It describes the design, not a shipped system; the acceptance test that checks it is listed as proposed below.

Party compromisedGetsDoes not get
GCP project or Identity Platform adminUser emails, login metadata, ciphertext, the ability to delete or roll back, the ability to log in as anyoneAny plaintext: the PRF output is bound to the origin and the user's authenticator
Bucket readerCiphertextPlaintext
Terraform pipelineCan change rules, delete the bucketPlaintext
This repository or the DNSEverything, for users who load the malicious pageNothing is withheld

The last row is why the repository protections in docs/ops/branch-protection.md exist. The code served to the browser is the boundary.

Shipped, proposed, absent

Every claim this site makes, with its status, from data/features.json. shipped exists and runs at the version shown; proposed is designed and not built; absent is deliberately not in the MVP.

AreaFeatureStatusWhereNotes
pipelineOne version in admin/build/version.txt, repeated in every badge, twin, index and configshipped v0.1.0admin/build/version.txtCheck 1 of the gate fails on any disagreement.
pipelineChrome (head, nav, footer, version badge, CSP, canonical) generated into every page from one definitionshipped v0.1.0admin/build/chrome.pygen_chrome.py --check is part of the gate.
pipelineMarkdown twin of every HTML page, links pointing at markdownshipped v0.1.0admin/build/gen_twins.pyindex.html has index.md beside it; the twin carries the site version.
pipelinellms.txt, llms-full.txt and sitemap.xml generated on every releaseshipped v0.1.0admin/build/gen_llms.pyllms-full.txt concatenates every twin and every design document.
pipelinedocs/reality.md and the status tables generated from data/features.jsonshipped v0.1.0admin/build/gen_features.pyIf the reality document does not list it, it does not exist.
pipelineThe eight-check release gate, no dependenciesshipped v0.1.0admin/build/validate.jsVersion agreement, internal links, canonical host, leak tripwire, vendor manifest, rules in sync, reality, storage keys.
pipelineThe same gate locally and in CI, one commandshipped v0.1.0admin/build/gate.pyThe CI validate job runs gate.py; a release that fails locally fails the same way in CI.
pipelineEvery push to dev is a release: version.txt and the commit subject agree, CI tags itshipped v0.1.0admin/build/tag_release.pyAnchors on the newest commit whose subject is 'site vX.Y.Z : ...', asserts the next minor or patch or major .0, backfills missing tags.
pipelineDeploy to GitHub Pages from the validated treeshipped v0.1.0.github/workflows/deploy-pages.ymlExcludes .git, .github, infra, tests/unit and node_modules. Actions pinned by commit SHA.
pipelineverify-live: the run is red until the live site serves the released versionshipped v0.1.0admin/build/verify_live.pyGreen does not mean live. Polls version.txt and the homepage badge for up to ten minutes.
pipelineEvery third-party file vendored and hashed in vendor/MANIFEST.json; no runtime script from another originshipped v0.1.0vendor/MANIFEST.jsonCheck 5 of the gate. Today the only vendored file is the family design tokens.
sitesecrets.sgit.ai served by GitHub Pages over HTTPSshipped v0.1.0CNAME, docs/ops/dns.mdverify-live passed on the v0.1.0 run (attempt 2, 2026-10-05) after the DNS record and Pages settings were made.
siteContent pages: how it works, security, keyring spec, sharing, environmentsshipped v0.1.1how-it-works/, security/, keyring/, sharing/, environments/Every page opens with a status line generated from this file and describes only designs in the future tense.
site/shipped/ generated from this file, one table per status, beside docs/reality.mdshipped v0.1.1shipped/index.htmlThe same generator writes both, so the page and the reality document cannot disagree.
siteThe five design documents published verbatimshipped v0.1.0docs/design/The markdown is the source of truth; since v0.2.0 each is also rendered to an HTML page beside it.
siteEvery markdown document under docs/ rendered to HTML on each release, with index pagesshipped v0.1.1admin/build/gen_docs.py, admin/build/md_to_html.pyStandard-library renderer; the markdown stays the twin. gen_docs --check is part of the gate.
siteThe family nav: grouped menus with dropdowns, part-of-sgit.ai link, stage pill, phone menu, breadcrumbsshipped v0.1.1admin/build/chrome.py, assets/nav.jsThe shape sgit.ai, nfrs.sgit.ai and pki.sgit.ai run; works with no JavaScript because every group label is a link.
adminComms page: the asks back to the project lead and the nine build steps with status, from data/steps.jsonshipped v0.1.1admin/comms.htmlgen_versions.py renders the step tracker; a done step must name a release that exists.
siteBranch protection, hardware-key 2FA, verified domain and the Actions policy in place and dated on /security/proposeddocs/ops/branch-protection.mdAsked for in docs/ops/needs.md. Each row on /security/ flips to a dated yes when confirmed.
sitebrief-corrections.md: what the brief got wrong, dated, beside itshipped v0.1.0docs/design/brief-corrections.mdAppended to as the build finds out.
sitedocs/ops/needs.md: exactly what only a human can doshipped v0.1.0docs/ops/needs.mdDNS, Pages, branch protection, GCP bootstrap, OAuth client secret.
infraBootstrap script for the tfstate project, env projects, Terraform service account and WIF poolproposedinfra/bootstrap/bootstrap.shStep 3. Run once by a human; idempotent; --dry-run.
infraTerraform module secrets-env and the dev environment rootproposedinfra/terraform/Step 3. Identity Platform, Firebase web app, bucket, rules release, IAM, WIF.
infrainfra.yml: plan on PR, apply on dispatch and on push to dev for devproposed.github/workflows/infra.ymlStep 3. Workload Identity Federation, no JSON keys.
infraStorage Security Rules deployed per environmentproposedinfra/rules/storage.rulesThe rules text is in the repository and hashed; nothing is deployed yet. Syntax to verify on a real project.
infraconfig/environments.json with real dev values from Terraform outputsproposedconfig/environments.jsonStep 3. Today every environment is a placeholder with _source null.
appSign in and out with Google and email/password against the chosen environmentproposedapp/index.htmlStep 3. Popup sign-in by default; vendored Firebase SDK.
appEnvironment page: pick a built-in environment, enter a custom one, import, export, resetproposedapp/environment.htmlStep 3. The active environment is shown in the header at all times.
appPasskey with WebAuthn PRF derives the keyring wrapping key; RP ID secrets.sgit.aiproposedapp/setup.html, app/unlock.htmlStep 4. Not before the dev project exists and the probe pages are green.
appKeyring v1 format: wraps per unlock method, AES-256-GCM body, known-answer testsproposedsection 8 of the briefStep 4. Published at /keyring/ as a specification.
appRecovery code: 26 characters base32, 128 bits, shown onceproposedapp/setup.htmlStep 4. Lose every passkey and the code and the data is gone; the site will say so.
appEntries: six kinds kept apart, vault list, entry page, copy and reveal, lock timersproposedapp/vault.html, app/entry.htmlStep 5. Kinds: password, api-key, sgit-vault-key, sgit-read-key, pki-private-key, note.
appOptimistic concurrency on keyring writes with a three-way mergeproposedsection 8.5 of the briefStep 5. rev in the AAD so a stale body fails to decrypt.
appDevices page: add and remove passkeys, regenerate the recovery codeproposedapp/devices.htmlStep 6.
appAccount page: export and import the encrypted keyring, sign out, wipe memoryproposedapp/account.htmlStep 6.
appKey pair per user generated at first run; public bundle written to directory/proposedsection 8.3 of the briefStep 4 generates the keys; step 9 writes the public bundle. Phase 1 data model for phase 2 sharing.
appSharing an entry with another user through their inboxabsent/sharing/ (step 2 describes it)Phase 2. The data model ships first so it is not rewritten later.
appA document kind in the keyringabsentsection 8.4 of the briefDocuments will be a pointer to an sgit vault plus that vault's key, not bytes in the keyring.
appBrowser extension or autofillabsentsection 12 of the briefA later site or a later version.
appAn API for agentsabsentsection 12 of the briefA later version.
appAny server-side code: Cloud Functions, proxies, a small APIabsenteverywhereForbidden by design. A need for one is a proposal in brief-corrections.md, not code.
adminAdmin sign-in with the visitor's own Google account, token in memory onlyproposedadmin/oauth.jsStep 7. Implicit flow to verify first; PKCE fallback.
adminSetup checklist: every per-project resource as a row, with Fix where fixable client-sideproposedadmin/setup-checklist.htmlStep 3 read-only, step 7 with fixes.
adminAuth config, storage, rules diff and deploy, users, environment exportproposedadmin/*.htmlStep 7. GCP IAM is the role; the pages are a client for GCP's own APIs.
adminRelease history page generated from data/versions.jsonshipped v0.1.0admin/versions.htmlOne row per release; the newest row must equal version.txt.
testsBrowser probe pages: webauthn-prf, crypto, config, auth, storage, keyring-roundtrip, offline, leak-check, matrixproposedtests/*.htmlSteps 3 to 6. Each prints PASS/FAIL/SKIP with the raw values; together they are the compatibility matrix.
testsUnit tests under node --test, real WebCrypto, no mocksshipped v0.1.0tests/unit/At this version: the gate's own checks against fake fixtures. Keyring tests come with step 4.
testsBuild tests: the generators run on the real tree, chrome in every page, twins exist, features schemashipped v0.1.0tests/build/pytest, TestCase classes, no mocks.
testsPlaywright end to end against the real dev project with a virtual authenticatorproposedtests/e2e/Whether the virtual authenticator does PRF is an open question.
testsAcceptance: an Owner of the dev project is handed a uid and asked to produce one plaintext fieldproposeddocs/acceptance.mdBefore 1.0. The write-up is published whatever the result.

Where to read next