For evaluators
What leaves the browser, and what never does
| Leaves the browser | Never leaves |
|---|---|
| The ciphertext (IV, encrypted bytes, GCM tag) | The plaintext |
| The plaintext length, as display metadata | The file name: it is inside the ciphertext, in the SGMETA envelope |
A content_type_hint the sender chooses; keep it generic | The decryption key: it is in the link's fragment, which browsers do not send |
| The access token, in a request header, to the API host only | The vault key, the read key, the keyring: this host receives nothing at all |
| For a vault: encrypted objects, refs and the write key's hex in a header, which the server stores only as a hash | The passphrase the vault key is made of |
The API host, api.send.sgit.ai, is GitHub Pages: it serves files and has no way to receive a request body. The server the modules talk to is send.sgraph.ai or whichever endpoint the page configures, and what that server can and cannot see is written up at sgit.ai's security page.
Two secrets that must not be confused
The access token says who may upload. The decryption key says who may read. They are unrelated values with unrelated jobs, and the failure that matters is silent: an access token in a URL lands in proxy logs and browser history; a key in a request reaches a server that should never have it. send-transfers.js puts the token in x-sgraph-access-token and nowhere else, and the tests assert that neither value appears in any logged request path.
The supply chain of a release
- Twelve vault library files from SGraph-AI__App__Send at a named commit, copied unchanged, with a SHA-256 per file in
VENDOR.md. The tests recompute every hash before loading a single one. - One QR encoder, qrcode-generator 2.0.4, with its hash in its README.
- Nothing else. No framework, no bundler, no build step for the served code, nothing fetched at run time from any other origin. The gate fails a page that loads a script or a style from another host.
- A manifest of every file in the release with its size and SHA-256, recomputed by the gate on every commit and by the live check after every deploy.
Verify the live host yourself
curl -s https://api.send.sgit.ai/releases.json | python3 -m json.tool
curl -s https://api.send.sgit.ai/v0.1.1/manifest.json > manifest.json
python3 - <<'PY'
import hashlib, json, urllib.request
m = json.load(open('manifest.json'))
for f, e in m['entries'].items():
b = urllib.request.urlopen(f"https://api.send.sgit.ai/v0.1.1/{f}").read()
print('ok ' if hashlib.sha256(b).hexdigest() == e['sha256'] else 'BAD', f)
PY
That is the same check tools/live_check.py runs at the end of every deploy. Until the DNS for this host is in place (what needs a human), run it against the repository instead: python3 tools/release.py check.
What this does not prove
- Interoperability with the live send.sgraph.ai is asserted from the published contracts and the in-memory server, not from a run against the production host. The reality file marks it proposed until a round trip is recorded.
- The keyring is plaintext at rest in the browser's storage, and the page that uses it says so. The passkey-unlocked design at secrets.sgit.ai is the stronger home.
- A friendly token is guessable offline by anyone holding the ciphertext: 1,024,000,000 possibilities and a 600,000-round derivation per guess. It is for links that expire.