{"id":49959292,"url":"https://github.com/sxwebdev/oblivio","last_synced_at":"2026-05-18T01:13:34.773Z","repository":{"id":356994647,"uuid":"1057547687","full_name":"sxwebdev/oblivio","owner":"sxwebdev","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-10T21:09:26.000Z","size":399,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-10T21:32:23.194Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sxwebdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-15T22:06:39.000Z","updated_at":"2025-09-16T15:30:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sxwebdev/oblivio","commit_stats":null,"previous_names":["sxwebdev/oblivio"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sxwebdev/oblivio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sxwebdev%2Foblivio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sxwebdev%2Foblivio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sxwebdev%2Foblivio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sxwebdev%2Foblivio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sxwebdev","download_url":"https://codeload.github.com/sxwebdev/oblivio/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sxwebdev%2Foblivio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33161453,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T22:39:12.733Z","status":"ssl_error","status_checked_at":"2026-05-17T22:39:10.741Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-18T01:13:34.134Z","updated_at":"2026-05-18T01:13:34.756Z","avatar_url":"https://github.com/sxwebdev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OBLIVIO\n\nOblivio is a self-hosted, multi-user, **zero-knowledge** password manager\n(server + WebUI). All sensitive material is encrypted on the client; the\nserver stores only ciphertext plus metadata and never sees plaintext\nsecrets, master passwords, vault keys, or item keys.\n\n## Features\n\n### Vault model\n\n- **Projects** — logical groups of entries.\n- **Entries** of multiple kinds (`login`, `totp`, `card`, `identity`,\n  `ssh_key`, `note`) stored as a single typed table; the `encrypted_blob`\n  contains kind-specific fields.\n- **One account = one user.** No organisations, teams, sharing, or roles.\n  Each user is an isolated vault.\n- **TOTP (RFC 6238)** — either embedded in a `login` entry or as a\n  standalone `totp` entry; codes are generated on the client.\n- **Optimistic concurrency** via `expected_version` on update/delete.\n- **Idempotency** for create/update via `Idempotency-Key` header\n  (24 h TTL, scoped per user and procedure).\n- **Real-time updates** via Server-Sent Events (SSE) backed by Postgres\n  `LISTEN/NOTIFY`.\n\n### Clients\n\n- **WebUI** — React + Vite + Tailwind + shadcn, served from the same\n  origin as the API (single binary, embedded static assets).\n- **Crypto core** — isolated TypeScript package (`@oblivio/crypto`)\n  used by the WebUI; the same primitives are mirrored in Go for round-trip\n  testing, and can be reused by future mobile / desktop / browser-extension\n  clients without changing the wire contract.\n\n### Transport\n\n- **ConnectRPC + Protobuf** (`buf`-generated stubs for Go and TS).\n- **Bearer-only auth** — `Authorization: Bearer \u003ctoken\u003e`. No auth cookies,\n  no implicit credentials, no CSRF surface.\n- **Same-origin deployment** by default (static + API on one host);\n  configurable CORS allow-list for split deployments.\n\n## Security\n\n### Zero-knowledge cryptography\n\n- **Two-stage KDF.** `master_key = Argon2id(master_password, salt_user)`\n  on the client; `auth_key = HKDF-SHA256(master_key, info=\"oblivio/auth/v1\")`\n  is what the client sends to authenticate. The server stores\n  `Argon2id(auth_key)`; reversing it would require breaking two KDF\n  layers.\n- **Key hierarchy:** `master → vault → item`. A random per-user\n  `vault_key` is wrapped under `master_key`; each entry has its own\n  random `item_key` wrapped under `vault_key`.\n- **Per-user Argon2id parameters** stored in the database, so the\n  cost can be raised over time without re-hashing existing material.\n- **Multi-thread Argon2id in the browser** when COOP/COEP/`crossOriginIsolated`\n  are active; single-thread fallback otherwise.\n\n### Encryption envelope\n\n- **AES-256-GCM** via WebCrypto on the client and `crypto/aes` on the\n  server. Envelope: `version(1) || nonce(12) || ciphertext || tag(16)`.\n- **AAD binding** on every operation:\n  - Items: `item_id || version || vault_id || \"item\"`.\n  - Wrapped keys: `parent_id || child_id || version || \"wrap\"`.\n  - Recovery wrap: `user_id || \"recovery\"`.\n    Any swap, rollback, or re-parent attempt fails AEAD authentication.\n- **Versioned crypto-protocol** (`crypto_protocol_version` in\n  `system_state`, `vault_key_version` per user) enables stepped\n  rollout of future algorithms.\n\n### Authentication \u0026 sessions\n\n- **Anti-enumeration** on `GetKDFParams`, `GetRecoveryParams`, and\n  `Authorize`. Unknown emails get stable pseudo-parameters (HMAC of\n  email + server secret) and a constant-time dummy Argon2id verify\n  so timing cannot distinguish unknown vs. wrong-password.\n- **Lockouts** on repeated authentication failures (`failed_attempts` +\n  `locked_until`).\n- **Sessions in Postgres** (`auth_sessions`), one row per user/device,\n  with hashed access/refresh tokens, expiry, and revocation columns.\n- **Refresh-token rotation with reuse detection** — replaying an old\n  refresh token revokes the entire session and emits a metric.\n- **JWT keys held in `memguard.LockedBuffer`** to resist swap/coredump\n  leakage on the server.\n- **Email verification** on registration with a single-use token.\n\n### Two-factor authentication\n\n- **TOTP (RFC 6238)** for sign-in.\n  - The TOTP secret is encrypted by the **client** with\n    `K_login_totp = HKDF(auth_key, \"oblivio/login-totp/v1\")` and only\n    transiently decrypted on the server during a sign-in attempt.\n  - The plaintext lives in a `memguard.LockedBuffer` and is destroyed\n    immediately after verification.\n  - A database-only attacker cannot derive the secret because it is\n    sealed under a key the server does not store.\n- **WebAuthn / Passkeys** (`github.com/go-webauthn/webauthn`).\n  - **Origin-bound** — passkeys cannot be replayed against a phishing\n    domain.\n  - `UV=required` is enforced on registration, enable, and unlock\n    ceremonies — authenticator possession alone never unlocks the\n    vault.\n  - `BackupEligible` / `BackupState` flag consistency is validated to\n    detect cloned or migrated credentials.\n- **Per-credential \"Use to unlock\" (PRF-based vault unlock).**\n  Optional, off by default. Stores a _second_ wrapping of `vault_key`\n  under a key derived from the WebAuthn **PRF extension** output. Useful\n  but widens trust to whatever protects the authenticator (provider\n  account, biometrics, hardware key + PIN). Users get an explicit\n  warning before enabling and can revoke all unlock bundles in one\n  click.\n\n### Recovery\n\n- One-time `recovery_code` generated by the **client** at registration\n  and shown to the user exactly once (no automatic clipboard write).\n- `recovery_key = Argon2id(recovery_code, recovery_salt)`; the server\n  stores `recovery_wrapped_vault_key` and `Argon2id(recovery_proof)`.\n- Recovery flow lets the user pick a new master password without\n  re-encrypting every item — only the `vault_key` wrappers are\n  rotated. All existing sessions are invalidated after a successful\n  recovery.\n\n### Server-side defense in depth\n\n- **Postgres Row-Level Security** on `projects`, `entries`,\n  `auth_sessions`, `user_webauthn_credentials`, `audit_log`, etc.\n  Every authenticated request runs inside a transaction that issues\n  `SET LOCAL app.current_user_id = $userID`; system jobs use a\n  separate `app.bypass_rls = on` path. A repository that bypasses the\n  interceptor sees an empty result set, not silent cross-tenant leakage.\n- **Anonymous allow-list** — every ConnectRPC procedure is\n  authenticated by default; only a hand-maintained list of public\n  procedures (register, login, KDF params, recovery start, etc.) is\n  exempt, and a test enforces it.\n- **Rate limiting** per IP and per email on `Authorize`,\n  `GetKDFParams`, `GetRecoveryParams`, `RecoveryStart`,\n  `CompleteMFA`, etc.\n- **Argon2id concurrency cap** (semaphore) so that login floods cannot\n  exhaust memory and CPU.\n- **Strict security headers** on every response, including static\n  assets:\n  - `Content-Security-Policy` with `default-src 'self'`, no\n    third-party CDN, `frame-ancestors 'none'`, `object-src 'none'`,\n    `base-uri 'none'`, `upgrade-insecure-requests`.\n  - `Strict-Transport-Security` (HSTS preload-ready).\n  - `Cross-Origin-Opener-Policy: same-origin`,\n    `Cross-Origin-Embedder-Policy: require-corp`,\n    `Cross-Origin-Resource-Policy: same-origin` —\n    enables `crossOriginIsolated` for multi-thread Argon2id.\n  - `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`,\n    `Referrer-Policy: no-referrer`, `Permissions-Policy` locking down\n    clipboard / interest-cohort, etc.\n  - Snapshot tests pin the exact header strings so any weakening is a\n    visible diff, not a silent regression.\n- **TLS `verify-full`** to Postgres; production deployments expect TLS\n  termination at the front edge.\n\n### Audit log\n\n- **Append-only** `audit_log` table with a **SHA-256 hash chain**:\n  `self_hash[i] = SHA-256(prev_hash || canonical_json(row[i]))`.\n- The current `audit_chain_head` is maintained in `system_state` under\n  a row lock; a periodic background job recomputes the chain and\n  alarms on mismatch.\n- **External anchor (optional):** an Ed25519 signer (`LocalSigner` on\n  disk by default; Vault Transit-ready) signs the chain head\n  periodically, so tampering that rewrites both rows and the cached\n  head is still detectable from outside the database.\n- **Crypto-shred-aware events.** `account_delete` is appended only on\n  successful deletion; every failed attempt emits\n  `account_delete_attempt_failed` with a `stage`\n  (`auth_key` / `totp` / `passkey`) and a `reason` in metadata. Probes\n  are forensically visible without polluting the success record.\n- Audit log is **read-only for the user** (via RLS); only the system\n  role can insert.\n\n### Client-side hardening (WebUI)\n\n- **Auto-lock** on inactivity, on `visibilitychange`, and on\n  `beforeunload` (synchronously zeroises `vault_key`).\n- **Clipboard auto-clear** 30 seconds after copying a secret; clears\n  only if the clipboard still contains the same value.\n- **No `localStorage`/`IndexedDB` persistence** for keys — `vault_key`\n  lives in a non-persisted Zustand store, in RAM only.\n- **Redux DevTools disabled** in production builds; no inline scripts;\n  no third-party CDN.\n- **Best-effort zeroisation** of `Uint8Array` key material; `CryptoKey`\n  objects are created with `extractable=false` where possible.\n\n### Account deletion\n\n- `DeleteMe` performs a **physical** cascade delete of the user,\n  vault, projects, entries, sessions, WebAuthn credentials, and audit\n  log. After the call, the server retains neither the ciphertext nor\n  the wrapped `vault_key`.\n- Honest caveat: database backups may still contain the encrypted\n  material until their retention expires.\n\n### Search\n\n- **Blind-index lookup** (`HMAC-SHA256(K_blind, NFKC(lowercase(title)))`)\n  for exact-match search over entry titles without exposing plaintext.\n  `K_blind` is derived per user from `vault_key`. Full-text search is\n  done on the client after decryption.\n\n### Observability \u0026 operations\n\n- **Prometheus metrics** for sign-in success/failure, refresh-token\n  rotation, decryption events, rate-limit drops, etc.\n- **Structured logs** that never contain plaintext secrets — only\n  metadata (user id, action, IP, user agent).\n- **Single-binary deployment.** The frontend is embedded via\n  `//go:embed`, so a production install is one binary plus a Postgres\n  connection.\n- **Configuration via `xconfig`** with optional HashiCorp Vault for\n  server-side secrets (JWT seeds, MFA KEK seed). Without Vault, the\n  daemon falls back to a `secrets/` directory with mode `0600`.\n\n## Usage\n\n```text\nNAME:\n   oblivio - Oblivio service\n\nUSAGE:\n   oblivio [global options] [command [command options]]\n\nVERSION:\n   version: local / revision: unknown / branch: unknown / pipeline ID: unknown / build date: unknown / go version: go1.26.3\n\nCOMMANDS:\n   start       start the oblivio service\n   config      configuration utilities\n   migrations  database migration commands\n   utils       custom cli utils\n   version     print current version\n   help, h     Shows a list of commands or help for one command\n\nGLOBAL OPTIONS:\n   --help, -h     show help\n   --version, -v  print the version\n```\n\n## Environment Variables\n\nEnvironment variables [available here](ENVS.md).\n\n## Security Policy\n\nSee [SECURITY.md](SECURITY.md) for the supported reporting channel,\ncoordinated-disclosure timing, threat model notes, and the full list of\nhardening choices and their honest trade-offs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsxwebdev%2Foblivio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsxwebdev%2Foblivio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsxwebdev%2Foblivio/lists"}