`logicsrc vault` (OpenCreds) lived only in ~/.config/logicsrc/opencreds.
Lose the machine and the vault went with it, and a second machine had
no way to get it. It now syncs to the logged-in account.
Server (apps/pwa, /api/opencreds, session or lsk_ bearer):
- opencreds_vaults: one per user. meta (key material already wrapped
under the master password) + an encrypted folder list, each with a
revision.
- opencreds_items: one row per item, as the spec asks; envelope NULL is
a tombstone so purges propagate; seq for incremental pulls.
- Every write names its base revision; a stale one gets 409 with the
current row. Writes are conditional and read back (each envelope's
random IV identifies our write), so this needs nothing dialect-specific
from SQLite or Postgres.
- Stores ciphertext only. The server learns item count and type codes,
as OpenCreds security.md already accepts.
Client (@logicsrc/opencreds sync.ts, key-free except where noted):
- Pull before every vault command, push after. Offline, the command
still works and the change goes up next time.
- Conflicts never lose data: the account's edit keeps the id and this
machine's becomes "<name> (conflict copy)" (needs the unlocked key, so
it waits otherwise). An edit beats a purge. Byte-identical envelopes
are adopted, not split, so a lost sync.json is harmless.
- Two different vaults (meta.createdAt differs) are never merged:
refused, with `vault sync --use-remote` (backs this machine's up to
.bak-NNN) or `--use-local`.
- Remote item ids must be plain ids; anything else is ignored and never
becomes a path.
- Folder names are AES-GCM encrypted under the user key before upload.
CLI (0.6.0): `logicsrc vault sync [--status|--use-remote|--use-local]`.
Only the default vault syncs, and only when logged in; a --home or
OPENCREDS_HOME vault stays local unless LOGICSRC_VAULT_SYNC=on;
LOGICSRC_VAULT_SYNC=off disables it. The standalone `opencreds` binary
gets no remote and never syncs. `init` on a machine that just downloaded
the account's vault says to unlock it instead of suggesting --force.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* pwa: read vault values in the browser with a pasted identity key
The web app listed vaults but could never show a value: decryption needs the
member's X25519 secret key, which only lives in identity.json on the machine
that ran `logicsrc login`.
Each vault now has a page (/teams/:slug/vaults/:id, linked from the
dashboard) listing its secret names. Paste the identity key (or the whole
identity.json) and public/vault.js decrypts in the browser with the same
libsodium calls as the CLI: crypto_box_seal_open for the grant, then
crypto_secretbox_open_easy per value. Nothing is sent to the server.
- The pasted key is checked against the public key the server has on file
before use, so a key from another machine is named, not a generic failure.
- Show / Copy per value, Download .env, Forget key. The key is kept in
sessionStorage unless "remember on this device" is ticked.
- A member with no identity yet can create one in the browser. That
registers only the public half, and is offered only when no key is
registered, since replacing one would orphan every grant sealed to it.
- libsodium is served from node_modules at /vendor (no CDN), like simplewebauthn.
- Vault pages are no-store and the service worker no longer caches no-store pages.
- CLI: `logicsrc teams key` prints the secret key to stdout alone (pipe to
pbcopy) and warns when the server holds a different public key. The page
also gives a jq one-liner for CLIs released before this command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* pwa: rename data-secret to data-key-name
ThreatCrush read data-secret="…" as a hardcoded credential (2 high, both
the attribute name, never a value).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CLI 0.5.0 for teams key; install via install.sh, not npm
@logicsrc/cli is not on npm; the CLI ships through
curl -fsSL https://logicsrc.com/install.sh | sh, which builds master and
reports the version from packages/cli/package.json. 0.5.0 marks the first
build with teams key, and the vault page now says so and points older
installs at the jq fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Patches GHSA-vcvr-r3jv-pc5j, GHSA-2xp9-vwfh-vxw4 and GHSA-p293-qw3h-jr36.
Next 16.3's build type check now fails on src/lib/pagination.test.ts,
which imports a .ts path; allow .ts import extensions (noEmit is already
on) so the build passes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Run on Bun: bun install, bun --bun next, both dev2 sites on Bun
Moves logicsrc.com and app.logicsrc.com (one image) off Node + npm onto Bun,
following the fleet recipe (phonenumbers.bot pilot).
- Package manager: bun.lock migrated from package-lock.json, so every resolved
version is unchanged; packageManager bun@1.4.2. Root scripts run each
workspace with `bun run --cwd <dir>` instead of `npm --workspace`.
- Bun 1.4.2, not the fleet's 1.4.0: 1.4.0 re-resolves this workspace's `file:`
cross-references differently on every install, so its own lockfile never
passes --frozen-lockfile. 1.4.2 is stable on it.
- Runtime: logicsrc-web runs `bun --bun next build/start`; apps/pwa (Express,
app.logicsrc.com) runs `bun src/server.mjs`.
- Image: .nixpacks/Dockerfile (the path both dev2 compose stacks build) is now
a hand-written oven/bun image carrying the whole workspace, run as the
non-root bun user. ENTRYPOINT stays `bash -l -c` so a compose `command:` is
one string, as before; app.logicsrc.com's compose starts it with
"npm --workspace @logicsrc/pwa run start", which .nixpacks/npm (the image's
only `npm`) turns into `bun run start` in apps/pwa. Port 3000, the PUBLIC_URL
build arg and the / health path are unchanged. The nixpacks .nix and
build.sh are gone.
- CI: both workflows install with bun and run every script through bun (Node
stays only as the interpreter for vitest/tsc/node --test/Playwright, as
before), and CI now boots the site under Bun.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ci: boot check on port 3100 and stop it before Playwright needs 3000
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(web): keep next dev on port 5174, which Playwright waits on
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>