Home / Security

Security

Status, from /shipped/: proposed Branch protection, hardware-key 2FA, verified domain and the Actions policy in place and dated on /security/ · proposed Acceptance: an Owner of the dev project is handed a uid and asked to produce one plaintext field · proposed Passkey with WebAuthn PRF derives the keyring wrapping key; RP ID secrets.sgit.ai

This page says what the design withholds from whom, and then says plainly what it cannot withhold. It describes a design. The acceptance test that will check it, an Owner of the GCP project being handed a uid and asked to produce one plaintext field, is listed as proposed above and its write-up will be published whatever the result.

What a compromised party gets

Party compromisedGetsDoes not get
GCP project, or the 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 to the user's authenticator; logging in as the user fetches ciphertext and nothing to open it with
Bucket readerCiphertext, object sizes and timesPlaintext
Terraform pipelineCan change the rules, delete the bucketPlaintext
Public-key directory tamperer (phase 2)Future shares, unless fingerprints or signatures are checkedExisting keys
This repository, or the DNS for sgit.aiEverything, for the users who load the malicious page while it is servedNothing is withheld

The code is the boundary

Client-side cryptography is exactly as trustworthy as the code delivered to the browser. If this repository, the GitHub organisation that owns it, or the DNS zone for sgit.ai is compromised, the attacker can serve a page that asks for your passkey gesture and sends the plaintext wherever they like. No amount of cloud hardening changes that, and the design does not pretend otherwise.

What it does instead is keep the site on a host that is separate from the GCP project, so that a cloud compromise does not reach the code, and put every guard it can on the repository:

GuardWhat it stopsIn place?
dev and main protected: pull request required, one review, validate required, linear history, no force-push, no bypass for administratorsA single account pushing code to usersUnconfirmed as of 2026-10-05; asked for in needs.md
Hardware-key two-factor authentication for every organisation memberA phished password becoming a pushUnconfirmed as of 2026-10-05
sgit.ai verified as an organisation domainAnother account claiming a dangling *.sgit.ai subdomain on PagesUnconfirmed as of 2026-10-05
Every third-party action pinned by commit SHA; minimal permissions per job; Actions may not approve pull requestsA compromised action or token widening its own reachPins and permissions: yes, since v0.1.0. Organisation policy: unconfirmed
No build step; every dependency vendored and hashed; the gate refuses any script from another originA supply-chain change arriving at runtime without a reviewed diffYes, since v0.1.0 (check 5 of the gate)
A leak tripwire over every file on every releaseA credential entering the public treeYes, since v0.1.0 (check 4)
Registrar lock, DNSSEC and a CAA record on sgit.aiThe zone being moved or a rogue certificate issuedThe DNS owner's decision; unconfirmed as of 2026-10-05
A Content-Security-Policy on every page: script-src 'self', style-src 'self', object-src 'none', base-uri 'none', form-action 'none'An injected script or form, if some other flaw let one inYes, since v0.1.0, on every page; frame-ancestors cannot be set in a meta tag, so app pages will add a frame-busting check

Each "unconfirmed" row flips to a dated "yes" when the person who can check it has. The repository's own copy of these settings is docs/ops/branch-protection.md.

The RP ID is secrets.sgit.ai, never sgit.ai

A WebAuthn passkey is scoped to a relying-party identifier, and the PRF secret it returns is derived per credential and per RP ID. The RP ID for the unlock passkey is exactly secrets.sgit.ai (and localhost when testing locally).

It is never the apex sgit.ai, and this is the single most important decision in the design. A passkey scoped to the apex can be asserted by any page on any subdomain: there are more than twenty-seven sibling sites under sgit.ai, each in its own repository, and a compromise of any one of them would then be able to ask for the gesture that unlocks every user's secrets here. Scoping to this host means a sibling's compromise is a sibling's problem.

Consumers such as riskmandate.ai will use their own passkeys, or WebAuthn Related Origin Requests, later. Neither is in the MVP.

What the passkey is, and is not, used for

The passkey is a plain WebAuthn credential registered by this site's own page. It is not an Identity Platform passkey and it is not a login. The page never verifies the assertion signature, because there is no server to verify it for: the proof that matters is that the PRF output unwraps the keyring key, and a wrong authenticator produces bytes that unwrap nothing. Login is a separate step, through Identity Platform, and decides only which bucket paths the browser may read and write.

The credential's id and public key are stored in meta.json so the page can list devices and build allowCredentials. The WebAuthn user handle is 32 random bytes, not the Firebase uid.

What we ask you to trust

What this page does not claim