# dove: condensed implementation context Status vocabulary: - SHIPPED: verified in current source. - DESIGNED: approved behavior that may not be released. - FUTURE: direction without an availability or timing promise. Source precedence: current tests/source, then current CLI help, then approved design documents, then marketing copy. Preserve conflicts; never merge planned syntax into shipped docs. ## Product and security boundary dove sends files and, in full tier, requests one back through infrastructure the operator controls. Simple tier stores an object in a private S3 bucket and prints a presigned URL. Full tier encrypts locally and uploads ciphertext; a gate enforces expiry, download/upload budgets, and optional PIN access without receiving the URL-fragment secret. A full link is `https:///d/#`. Browsers and ordinary HTTP clients do not send the fragment. The default page reads it locally. Filename, sender name, and message are encrypted with a domain-separated metadata key and stored server-side only as opaque ciphertext. Full file container, big-endian: `"DOVE" | version:u8 | nonce_prefix:[u8;8] | chunk_size:u32`, followed by repeated `is_last:u8 | ct_len:u32 | AES-256-GCM ciphertext_and_tag`. Default plaintext chunk size is 1 MiB. Nonce is the random prefix plus a u32 counter. Counter and `is_last` are AAD, so changes, reordering, and truncation fail authentication. Outgoing PIN shares derive the content key with PBKDF2-HMAC-SHA256(PIN, salt=fragment_secret), 600,000 iterations. The gate verifies submitted PINs online against a stored SHA-256 ID-bound verifier and locks after five wrong attempts. File requests use the PIN only for upload authorization; the request fragment secret is the content key. The gate sees a submitted PIN but not the fragment. Non-guarantees: simple plaintext is readable by infrastructure; a recipient can copy decrypted bytes; traffic metadata and object size remain; a compromised endpoint can read local secrets; an operator controls its own HTML and a malicious page could exfiltrate the fragment. The server-cannot-read claim assumes the audited default page or a customization that preserves the no-exfiltration contract. ## SHIPPED CLI `dove provision [--bucket NAME] [--region REGION] [--profile PROFILE] [--expire-days DAYS]` - region default us-east-1; expire-days default 7 - profile selection and final confirmation are interactive when applicable `dove share [--expires DUR] [--encrypt] [--downloads N] [--pin [VALUE]] [--from NAME] [--message TEXT]` - expires default 3d; duration is positive integer plus d/h/m/s - directory is zipped; symlinks skipped - full always encrypts; default full download budget 100 - bare --pin generates a six-digit PIN - PIN, from, and message are rejected outside full. Downloads has an effect only in full; the current simple path silently ignores it. Zero downloads creates no usable budget. - a supplied PIN is not CLI-validated; use 4-6 numeric digits for browser compatibility. Simple expiry max seven days. `dove get [-o|--out PATH] [--pin PIN]` - requires no provisioned backend `dove request [--from NAME] [--message TEXT] [--pin [VALUE]] [--expires DUR] [--uploads N]` - full tier only; expires default 3d; uploads default 1 - bare --pin generates a six-digit PIN - prints /r/#fragment and writes the fragment to the local request ledger - current browser path accepts one file up to 512 MiB; this is not a multi-file inbox `dove requests` `dove requests get [-o|--out PATH]` - list uses local requests.json plus live gate status - get downloads/decrypts a received request with the locally retained fragment `dove ls` `dove revoke ` `dove status` `dove use ` `dove domain add ` `dove gate disable|enable|status` Missing: stable --json, stdin upload, batch sharing, multi-file request inbox, unattended provision, destroy. ## SHIPPED gate routes - `GET /d/`: static HTML page; no policy decrement. - `GET /meta/`: expiry, size, budgets, opaque metadata, pin_required, locked; no decrement. - `GET /verify/?pin=...`: PIN rate-limit/lock pre-check; no decrement. - `GET /dl/?pin=...`: policy check and atomic decrement, then 302 to 15-minute presigned S3 GET. - `GET /og.png`: generic share preview image. - `GET /r/`: static request upload page; no upload authorization. - `GET /rmeta/`: request status and opaque encrypted metadata; no upload authorization. - `GET /up/?pin=...`: authorize an attempt and return a short-lived presigned S3 POST form. - `POST /done/?pin=...`: confirm the object and encrypted filename metadata; completes the request. - `GET /og-request.png`: generic request preview image. The browser must not call `/dl` or `/up` on load. Outgoing shares below 2 GB use WebCrypto; at 2 GB and above the page shows the complete `dove get ` handoff. Request uploads are one browser-buffered file up to 512 MiB and start only after explicit file selection and an Encrypt & send action. ## SHIPPED core API `Transfer`: - `share(&self, ShareRequest, &dyn Progress) -> Result` - `get(&self, GetRequest, &dyn Progress) -> Result` - `list(&self) -> Result>` - `revoke(&self, id: &str) -> Result<()>` - `status(&self) -> Result` `ShareRequest`: path PathBuf, expires Duration, encrypt bool, downloads Option, pin/from/message Option. `Share`: id String, link String, size u64, expires_at u64. `GetRequest`: url String, out Option, pin Option. `Fetched`: path PathBuf, from/message Option. `Progress`: step(label), done(label), field(key,value), bytes(uploaded,total). `Silent` is provided. File-request methods on `SelfHosted`: - `create_request(&self, CreateRequest, &dyn Progress) -> Result` - `request_status(&self, &RequestRecord) -> Result` - `collect_request(&self, &RequestRecord, Option, &dyn Progress) -> Result` - `CreateRequest`: description String, from/message/pin Option, expires Duration, uploads u32. - `RequestStatus`: Waiting, Received { name, size }, Failed { reason }. These are not methods on the generic Transfer trait in the current source. Resolve active backend with `dove_core::resolve(&Registry)`. `self-hosted` works. External `dove-` discovery exists, but subprocess dispatch/wire protocol is not implemented. Provisioning core: - `provision_simple(&ProvisionArgs, Option, &dyn Progress) -> anyhow::Result` - `provision_full(&ProvisionArgs, Option, Option, &dyn Progress) -> anyhow::Result` - ProvisionArgs: bucket Option, region String, expire_days u32. Core assumes the caller selected identity/confirmed and returns config; caller persists it. ## SHIPPED provisioning Simple: private S3 bucket, all public access blocked, lifecycle cleanup, scoped IAM user/key limited to S3 Get/Put/Delete/List on one bucket. Secrets at `~/.config/dove/secrets.toml` (0600 Unix). Config at `~/.config/dove/config.toml`. XDG_CONFIG_HOME relocates both; DOVE_CONFIG overrides only config registry. Full adds S3 CORS for browser GET and presigned POST, DynamoDB PAY_PER_REQUEST table with TTL, gate IAM role with bucket-scoped Get/Put, stable HMAC secret in SSM SecureString, Python 3.12 gate Lambda with embedded share/request pages and preview images, API Gateway HTTP API, CloudFront with a request POST behavior, and cost breaker. Stage throttle is burst 50/rate 25 per second. Breaker triggers above 5,000 invocations in five minutes and sets gate concurrency zero. `dove gate enable` removes the override. Provision is idempotent where implemented: reuses resource names, existing access key, API, distribution, gate secret, and custom gate URL. No destroy command exists. ## Custom page checklist Current customization is source-level: build a self-contained recipient page, replace `dove-core/assets/share.html`, test, then re-run `dove provision full` to update Lambda. No theme manifest/runtime plugin exists. Preserve: no fragment/PIN/plaintext logs or network egress; no download decrement or upload authorization on load; explicit human action; local cryptography; authenticated chunk order/termination; PIN remaining-attempt and terminal locked UI; expiry/gone/limit/integrity/network states; browser accessibility; outgoing-share 2 GB handoff; request one-file/512 MiB ceiling; canonical install pointer to dove.sh; no third-party scripts by default. ## SHIPPED file requests `dove request` creates a full-tier request, encrypts requester trust metadata with a new fragment secret, stores that fragment only in the printed link and the requester’s local `~/.config/dove/requests.json` ledger, and prints the optional generated PIN separately. The link is `https:///r/#`. The uploader needs only a browser. The request page loads freely, decrypts requester name/message/description locally, verifies any PIN without starting an upload, then waits for explicit file selection and Encrypt & send. It encrypts one file in memory using the fragment secret and the dove chunked container, obtains a gate-scoped presigned S3 POST, uploads ciphertext to a gate-chosen object key, and confirms completion. Current ceiling: 512 MiB. `dove requests` lists local records with live gate status. `dove requests get ` downloads and decrypts a received object. Losing requests.json loses this machine’s durable fragment copy; the gate cannot recover it. Values of --uploads above 1 do not create a multi-file inbox in the current v1 flow.