How the Encryption Works
SG/Send uses AES-256-GCM symmetric encryption, performed entirely in your browser. The server never sees your plaintext data, your file names, or your encryption keys. This page explains exactly how.
Symmetric Encryption: One Key, Two Operations
SG/Send uses symmetric encryption — the same key encrypts and decrypts the file. This is the simplest and fastest encryption model, and when done correctly, the most secure for file transfer.
What is AES-256-GCM?
AES (Advanced Encryption Standard) is the encryption algorithm used by governments, banks, and militaries worldwide. 256 is the key size in bits — there are 2256 possible keys, making brute force computationally impossible. GCM (Galois/Counter Mode) adds authenticated encryption — it doesn't just encrypt, it also detects if anyone has tampered with the ciphertext.
Why Symmetric?
Symmetric encryption uses one key for both encryption and decryption. This is different from asymmetric encryption (like RSA) which uses a key pair. For file transfer, symmetric is the right choice: it's fast, well-understood, and the Web Crypto API provides a battle-tested implementation in every modern browser.
The Encryption Flow: Step by Step
Here's exactly what happens when you send a file through SG/Send.
Key Generation
When you select a file, your browser generates a random 256-bit AES key
using the Web Crypto API (crypto.subtle.generateKey).
This uses the operating system's cryptographically secure random number generator.
The key exists only in your browser's memory.
IV Generation
A 12-byte Initialisation Vector (IV) is generated randomly
(crypto.getRandomValues). The IV ensures that encrypting the same
file twice produces different ciphertext. The IV is not secret — it is prepended
to the encrypted output and sent with the ciphertext.
Encryption
The file is encrypted in the browser using crypto.subtle.encrypt
with the AES-GCM algorithm, the generated key, and the IV. The output is
ciphertext + a 128-bit authentication tag. The authentication tag
is GCM's integrity guarantee — if anyone modifies even one bit of the ciphertext,
decryption will fail.
Upload
Only the encrypted output (IV + ciphertext + auth tag) is uploaded to the server. The server receives a blob of random-looking bytes. It assigns a Transfer ID (12 random characters) and stores the ciphertext. No file name, no content type hint, no key — just encrypted bytes.
Link Sharing
The sender gets a download link. The decryption key is placed in the
URL fragment (the part after #). URL fragments are
never sent to the server by the browser — this is defined in
RFC 3986.
The sender shares this link through any channel they choose (email, chat, in person).
Decryption
The recipient opens the link. Their browser downloads the encrypted bytes from
the server, extracts the key from the URL fragment, separates the IV from the
ciphertext, and decrypts using crypto.subtle.decrypt. If the auth tag
doesn't match (tampering detected), decryption fails. The server never participates
in decryption.
What the Server Sees vs. What It Doesn't
This is the zero-knowledge guarantee. Here is exactly what data exists on the server.
Server stores
Transfer ID — random 12 characters
Encrypted bytes — indistinguishable from random data
IP hash — SHA-256 of IP + daily rotating salt
Timestamp — when the transfer was created
File size — size of the encrypted blob (bytes)
Server never sees
Encryption key — stays in browser, shared via URL fragment
File name — never sent to server
File type — no content-type metadata stored
Plaintext content — only ciphertext ever uploaded
Raw IP address — hashed before storage
Technology Stack
Every layer is chosen for simplicity, security, and zero-dependency deployment.
| Layer | Technology | Purpose |
|---|---|---|
| Runtime | Python 3.12 / arm64 | Application server language |
| Web Framework | FastAPI via osbot-fast-api | HTTP routing and request handling |
| Compute | AWS Lambda + Mangum | Serverless execution, pay-per-use |
| Storage | Memory-FS (Storage_FS) | Pluggable backend: memory, disk, or S3 |
| Encryption | Web Crypto API (AES-256-GCM) | Client-side encryption in the browser |
| Type System | Type_Safe (osbot-utils) | Schema definitions without Pydantic |
| Frontend | Vanilla JS + Web Components | Zero framework dependencies (IFD) |
| Testing | pytest, in-memory stack | No mocks, no patches, real implementations |
| CI/CD | GitHub Actions | Test, tag, deploy pipeline |
System Architecture
Two Lambda functions, one storage layer, one CDN. Simple by design.
User Lambda (Public)
Public-facing function that handles file transfers, health checks, presigned uploads, personal vaults, and MCP integration. Accessed via Lambda Function URL behind CloudFront.
18 endpointsAdmin Lambda (Authenticated)
Admin-only function for token management, analytics, server stats, and MCP admin tools. Requires authentication. Separate Lambda = separate security boundary.
55 endpointsMemory-FS (Storage_FS)
All storage goes through an abstraction layer. Application code never knows if the backend is in-memory, on disk, or S3. Same codebase across all 7 deployment targets.
Pluggable BackendCloudFront CDN
Static assets, caching, SSL termination, and WAF. Lambda URLs provide the HTTPS endpoints directly — no API Gateway needed.
Edge NetworkDeployment Targets
One codebase, seven deployment targets grouped into four patterns.
Lambda
Primary deployment. Two Lambda functions behind Lambda Function URLs. Direct HTTPS endpoints, no API Gateway needed.
ProductionContainer
Docker, AWS Fargate, and GCP Cloud Run. The same application packaged as a container.
Docker / Fargate / GCPServer
EC2 instances and AMI builds. For teams that need full control of the runtime environment.
EC2 / AMICLI
Command-line interface for scripted transfers, CI/CD pipelines, and power users.
Terminal