Repository navigation
Releases
Releases · fxerkan/concealer
concealer 1.0.0
[1.0.0] — 2026-09-27
First public release. The desktop tool and the iOS/Android mobile app ship on the
same 1.0.0 version line.
Added
- Mobile app (iOS + Android) — a Capacitor companion app that opens the vault
offline with your master password, works standalone, and syncs bidirectionally
with the host (POST /api/sync, timestamp last‑write‑wins with deletion tombstones).
All crypto runs on‑device (age-encryption); only master‑password‑encrypted bundles
cross the wire. Includes an offline demo mode (no host needed). - Automated AI‑agent registration —
concealer agent install [--all]detects the AI
agents on your machine (Claude Code, Cursor, Windsurf, Gemini CLI, Claude Desktop, Codex),
registers a per‑agent token, writes the MCP config non‑destructively, and installs the
concealer skill where supported. - Auto‑start service —
concealer service install|uninstall|statusruns thelan
bridge at login as a per‑user background service (macOS launchd, Linux systemd--user,
Windows Scheduled Task) so phones can always reach and auto‑discover the vault. - LAN discovery —
concealer lanadvertises the vault over mDNS/Bonjour
(_concealer._tcp) with a stable vault id, so a phone picks the vault by identity
rather than by ip/host/port (survives DHCP changes and multiple vaults). - Chrome extension — copy secrets per‑field straight from the toolbar; talks only to the
local127.0.0.1server via a built‑in native host (concealer native-host), starts the
web server on demand and self‑exits after idle.
concealer 0.9.29
[0.9.29] — 2026-09-26
Added
- Demo mode — a friction-free way to explore concealer with sample data, no real vault
or host needed.concealer demo [port]spins up an isolated throwaway vault (temp dir,
master passworddemo1234), seeds six dummy secrets, and serves the web UI with a DEMO
banner. The mobile app gains a matching "Try the demo — no host needed" button on the
setup screen that loads the same sample set offline (a DEMO badge shows in the header; sync
is disabled). All demo values are dummy (DEMO/sk_test_…).
concealer 0.9.28
Rollup release: folds in 0.9.25, 0.9.26 and 0.9.27, which were developed between 0.9.24 and this release and never cut their own tag.
[0.9.28] — 2026-09-23
Added
- Auto-start service —
concealer service install|uninstall|statusruns thelanbridge
automatically at login as a per-user background service (macOS launchd, Linux systemd--user,
Windows Scheduled Task), so phones can always reach and auto-discover the vault with no
manualconcealer lan. Prefers the globally installedconcealerso it opens the real vault. - LAN discovery —
lannow advertises the vault over mDNS/Bonjour (_concealer._tcp, via
dns-sd/avahi-publish-service, best-effort) with a stable vault id so a phone can pick
the vault by identity rather than by ip/host/port (handles DHCP changes and multiple vaults
on one network). New unauthenticatedGET /api/serverinforeturns{id, name, version}(no
secret) for labeling discovered servers and validating a manually entered address. The id is
minted once and stored inkeys/serverinfo.json(git-ignored). - Mobile app (iOS + Android) — a new
mobile/Capacitor app that opens the vault
offline with the master password, imports the whole vault from the host on first
connect, lets you add/edit/delete secrets on the phone offline, and syncs bidirectionally
when online. All crypto runs on‑device via theage-encryptionJS library (same age
passphrase format as concealer bundles — no sops/age binary on the phone). At rest the
phone stores only the master‑password‑encrypted bundle. Seemobile/README.md. - Bidirectional device sync — foundation for the mobile app. A new authenticated
POST /api/syncendpoint exchanges an encrypted vault bundle both ways in one call:
the peer uploads its bundle (encrypted with the master password), the host does a
last-write-wins merge (newerupdatedtimestamp wins), saves, and returns the full
merged set (bundle) so the peer converges to the same state. Secret values never cross
the wire in plaintext — the sync payload uses the same portable bundle encryption as
export. Requires the master password (human-owner operation), audited assync. - Deletion sync via tombstones — deletes are now soft: a deleted record keeps its id,
name and scope (all secret values dropped) and is marked with adeletedtimestamp so
the deletion can propagate to other devices. Tombstones never appear in lists, search,
get, the web UI, MCP enumeration, or human backups/exports — only in device sync.
Fixed
rm --name Xcould delete the wrong record(s) on a vault with two or more live
records. The scope selector was rebuilt inside a matching loop and the builder mutates
its argument list, so after the first row the selector became empty and matched every
remaining record (e.g.rm --name gammareporting "2 records matched"). The selector is
now computed once.
[0.9.27] — 2026-09-23
Added
concealer agent install [--all|--mcp-only|--skill-only] [ids]— auto-detects the
AI agents installed on this machine (Claude Code, Cursor, Windsurf, Gemini CLI, Claude
Desktop, Codex), lets you pick which to set up interactively (or by id /--all), then
wires concealer's MCP server into each one's config with a fresh per-agent token and
installs the concealer skill where supported (Claude Code). Replaces the manual
agent register+claude mcp addtwo-step. Config merges are non-destructive (existing
MCP servers preserved) and idempotent. Tokens stay client-side; revoke with
concealer agent revoke <id>.
[0.9.26] — 2026-09-21
Added
concealer lan [lan_port [web_port]]— reach the web UI from a phone (or any
device) on the same local network.webbinds loopback only and rejects
non-loopback Host/Origin (anti-DNS-rebinding), so it is unreachable from a phone by
design.lanruns a thin reverse proxy that accepts only private/loopback/link-local
clients, rewrites Host/Origin back to loopback, and forwards to a localweb
instance (auto-started if not already running, using the installed vault). The master
password stays the only gate; the proxy holds no vault state or key. Phone URL is
http://<host>.local:<lan_port>(default 8788 → web 8787) — resolvable over Bonjour/mDNS
with no extra DNS setup. Intended for a trusted LAN (plain HTTP).
[0.9.25] — 2026-09-15
Changed
- Default CLI unlock-token lifetime is now 1h (was 8h), overridable with
CONCEALER_TOKEN_TTL(seconds). Shorter default = smaller window if a shell holding the
token is exposed.unlocknow also exportsCONCEALER_TOKEN_EXP(expiry epoch) so a
shell hook can auto-unsetthe token string once the server-side TTL lapses (the token is
already rejected after expiry — a child process cannot unset a var in its parent shell).
Security
- The TUI now always requires the master password. It previously honored a
CONCEALER_TOKENin the environment and would open — and reveal secrets — with no
prompt, so any shell holding an unlock token could browse the vault. The TUI now ignores
the token and unlocks from the master password on the terminal, matching the web UI's
per-session unlock. (Token-drivenget/list/MCP are unchanged.)
Fixed
run_with_secrets/runnow inject usable env vars for secrets whose name isn't a
valid shell identifier. A secret namedgrafana-rpifxpreviously emitted
grafana-rpifx_PASSWORD— not referenceable by the child ($grafana-rpifx_PASSWORD
doesn't parse,compgen -ewon't list it), sowebsite/loginand other hyphenated
secrets looked "not injected". Names are now sanitized to valid identifiers
(grafana-rpifx→GRAFANA_RPIFX_PASSWORD). Colliding names fail loudly instead of
silently clobbering.
Added
CONCEALER_INJECTED— injected into the child env as a comma-separated list of the
injected identifier names (names only, never values), and shown on aninjected:line
in therun_with_secretsMCP result, so a script/agent can discover what was injected
without guessing.env_aliason a record — pin a stable env-injection base (e.g.grafana-rpifx→
GRAFANA) for scripts that reference fixed variable names. Settable viaset_secret.- Secure Agentic Autofill (fill-without-reveal). A new MCP tool
request_web_login
lets a browser-driving agent sign in to a site with a storedwebsite/loginsecret
without the value ever entering the agent/LLM context. The agent only asks; a human
approves in the concealer web UI (approve once / for this task / deny, with an
anti-phishing domain match); the concealer browser extension injects the credentials
into the page's login form and the value flows app → extension → DOM only. Every step is
audited (web_autofill_request/approve/deny/fill/exposed) and the agent receives only a
status string. The extension gains a background service worker + content script (form
detection, native-setter fill withinput/changeevents, and a post-fill DOM exposure
scan). Loopback endpoints/api/autofill/{request,pending,jobs,approve,deny,claim,result,status}
carry the handshake; the MCP process reaches the web server onCONCEALER_WEB_PORT
(default8787).
concealer 0.9.24
[0.9.24] — 2026-09-09
Fixed
- Web UI showed a blank “webui.html not found” page after an in-place upgrade. A
concealer web
server started before the upgrade keeps running from its versioned install dir, which the package
manager deletes — so it can no longer read its ownwebui.html. The server now falls back to the
current install’s copy (resolved via theconcealer/ceronPATH) and, if truly gone, shows an
actionable “quit and runcer webagain” message instead of a blank page. (Existing stale servers
from before this version must still be quit once — this heals the next upgrade.)
Changed
- Chrome extension no longer stalls on open with a large vault. The popup rendered a DOM row for
every secret at once, which felt like a hang. It now renders the first 50 matches and shows
“showing 50 of N — type to filter”; the search box still filters the whole vault instantly.
concealer 0.9.23
[0.9.23] — 2026-09-08
Fixed
- Settings page lost the auto-backup config (and HIBP key status) on every refresh.
The i18n pass settextContenton every[data-i18n]element, which wiped the dynamic
<span>children living inside labelled nodes (#hibp_set,#bk_pw_set,#pol_badge).
With#hibp_setgone,loadSettings()threw before it reachedloadBackup(), so the
backup fields were never populated and looked empty/unsaved even though the config was on
disk. The translation now updates only the leading text node when an element has child
elements, preserving those spans.
Added
- The Settings page footer now shows the brand + version + GitHub link, matching the login screen.
- Clicking the concealer logo/name (top-left) returns to the Secrets home view.
- Landing page: the “concealer” wordmark is marked
translate="no"so a browser’s built-in
page-translation leaves the brand intact instead of mangling it (e.g. into “gizlemeker” in Turkish).
The brand stays “concealer” in every language.
Fixed
- Chrome extension stuck on the old version / "webui.html not found" after an upgrade.
Aconcealer webserver started before the upgrade keeps running from the now-deleted
install directory, so it can no longer find its ownwebui.html. The extension's native
host saw the port already open and blindly reused that dead server. The native host now
checks the running server's version (newversionfield in/api/session); if it does
not match, it asks the old server to stop (new/api/shutdown) and launches the current
one. Upgrades from this version on heal automatically; a server already running from a
pre-0.9.22 install must be quit once (or reboot) — the popup now says so instead of
showing a blank page.
Added
- The extension shows a version-mismatch banner when its version differs from the running
app, with a reminder to update concealer (brew/scoop/pip) and reload the extension. concealer version --check— opt-in, on-demand check for a newer release. concealer makes
no network calls unless you run this; there is no automatic/background update polling.
concealer 0.9.22
[0.9.22] — 2026-09-08
Fixed
- Chrome extension stuck on the old version / "webui.html not found" after an upgrade.
Aconcealer webserver started before the upgrade keeps running from the now-deleted
install directory, so it can no longer find its ownwebui.html. The extension's native
host saw the port already open and blindly reused that dead server. The native host now
checks the running server's version (newversionfield in/api/session); if it does
not match, it asks the old server to stop (new/api/shutdown) and launches the current
one. Upgrades from this version on heal automatically; a server already running from a
pre-0.9.22 install must be quit once (or reboot) — the popup now says so instead of
showing a blank page.
Added
- The extension shows a version-mismatch banner when its version differs from the running
app, with a reminder to update concealer (brew/scoop/pip) and reload the extension. concealer version --check— opt-in, on-demand check for a newer release. concealer makes
no network calls unless you run this; there is no automatic/background update polling.
concealer 0.9.21
[0.9.21] — 2026-09-08
Fixed
- Chrome extension stopped working after upgrading concealer ("Couldn't reach the
concealer server (Native host has exited.)"). The registered native-host launcher
recorded the resolved script path, which package managers place under a versioned
directory (…/Cellar/<ver>/libexec/concealer, scoop'sapp/<ver>); the next upgrade
deletes that directory, so the helper died the instant Chrome launched it — and with
the helper dead the popup never reached the master-password step either. The launcher
now records the stable command onPATH(which package managers re-point on upgrade)
and re-resolves at run time if that is gone. concealer webnow repairs a launcher left dangling by an earlier upgrade, so an
affected install heals itself on the next run.- The extension popup shows the setup card (with the
cer chrome-extensioncommand)
for any native-host failure, not just an unregistered host — previously a broken
helper produced only a raw Chrome error with no hint of the fix.
concealer 0.9.20
[0.9.20] — 2026-09-08
Fixed
- Import in the web UI did nothing when clicked. The file was base64-encoded
withbtoa(String.fromCharCode(...new Uint8Array(buf))), which blows the JS call
stack for any file over ~100 KB — a real.cerbakis far bigger, so the reader
callback threw before the password prompt could open and the click looked inert.
Encoding is now chunked (b64enc()), and read/encode failures surface a toast
instead of dying silently. - Import now asks for "password of the file (backup / export password)" rather
than "master password" — a.cerbakis opened with the password it was written
with, which is usually not this machine's master password. concealer initcould overwrite a hardened vault. The "already set up" guard only
checkedkeys/age-key.txt, which hardening deliberately removes — so on a hardened vault
initsailed past the guard and generated a fresh keypair, orphaning every secret. It now
also checks the encrypted key backup.- Login background: redaction bars no longer draw a line through themselves.
Each redacted fragment painted a solid bar (.rx::after) and a second
strike-through rule (.rx::before,rxstrike) across its middle, so a covered
secret read as struck-out rather than concealed. The strike layer is gone — a
redaction is now just the bar. Same fix in the docs landing page, which carries
a copy of the animation.
Changed
- No user-facing surface names the underlying crypto tools any more. The CLI banner,
--helpheader, TUI splash,init/hardenoutput, dependency-missing message, web UI
warnings and every error string now say "vault key" / "encryption engine" instead of
naming SOPS or age. Subprocess stderr is passed through a newscrub()at each boundary
that reaches a user (CLI exits, web/api/*errors, MCP tool errors, TUI status line), so
a leaking tool error can't reintroduce the names. Implementation is unchanged — encryption
is still delegated, and internal paths, env vars and argv keep their real names. - One extension everywhere:
.cerbak. Export no longer writes.age— the web
and CLI both produceconcealer-export-YYYY-MM-DD.cerbak, in the same opaque
container as backups. Buttons, hints, file picker and--helpfollow. Existing
.age/.cerfiles still import unchanged (the reader auto-detects the format).
concealer 0.9.19
[0.9.19] — 2026-09-02
Changed
- Secrets table columns no longer auto-flood. Custom-field columns now use an
allowlist (sc_col_shown, default = base columns only), so newly-appearing
field columns stay hidden until the user opts them in — instead of dozens of
columns sliding in on every load. - Columns picker moved from the table header ⚙ into a toolbar button next to
New Secret / Scan folder / Generate. The modal gained Select all / Default /
None buttons, per-type template presets (pick a type → show its standard
fields), and a live search box. - Risks → Exposure (online leak check) now requires narrowing: at least one
filter (scope/collection/tag) must be selected before any record list is shown,
and Scan leaks stays disabled until at least one record is picked — no more
implicit "none picked = scan everything". - Secrets table is now a proper frozen-column grid. Columns size to their
content instead of crushing into the viewport (which word-wrapped headers/values
into an ugly stack). Horizontal scrolling is contained inside the table — the page
no longer shifts sideways — with the NAME column frozen on the left, ACTIONS frozen
on the right, and everything between scrolling. NAME is never auto-truncated. - Audit Logs columns picker moved from the table-header ⚙ into a toolbar button
(next to Verify chain / CSV / JSON), matching the Secrets page. The audit grid now
auto-sizes columns to their content (was fixed-width), so TIME no longer word-wraps.
Security
- Secret field values are fully masked (
••••••••) in the secrets list, instead
of the partialsk-D…xymask that leaked the first/last characters. The partial
mask still appears in detail/risk views where identifying the value is the point.
concealer 0.9.18
[0.9.18] — 2026-09-01
Security
- Hardened the web UI against XSS (OWASP audit F1/F5). A secret's
urlfield is now passed through a
scheme allow-list (safeUrl→ onlyhttp/https/mailto) before it can become a clickable link, so a
storedjavascript:/data:URI (e.g. planted via the MCPset_secreturlarg) can no longer execute
when the owner clicks it.esc()now also escapes'and backtick, and the copy-value button builds its
handler argument viaJSON.stringifyinstead of hand-rolled quote-escaping (fixes a backslash/newline
breakout).rel="noopener"added to the remainingtarget="_blank"links. - Content-Security-Policy + security response headers (F2/F3). The web server now sends
Content-Security-Policy
(default-src 'none',connect-src 'self',frame-ancestors 'none', …;script-src/style-srckeep
'unsafe-inline'until the inline handlers are refactored), plusX-Content-Type-Options: nosniff,
X-Frame-Options: DENY,Referrer-Policy: no-referrer, andCache-Control: no-storeon every response.
A matching CSP<meta>is embedded inwebui.htmlas a fallback. - Anti-DNS-rebinding guard (F4). The localhost API now rejects requests whose
Hostis not
127.0.0.1/localhostand whoseOrigin(when present) is neither loopback nor achrome-extension://
page, returning403. Blocks a malicious web page / DNS-rebinding attack from driving the API in the
victim's browser. - Stronger master-password KDF (F6). The
master.jsonverifier now uses scryptN=2^17(was2^14),
matching current OWASP guidance;maxmemscales with the cost factor. High-entropy token/recovery hashes
keep the cheaper factor. Existing vaults are lazy-upgraded to the strongerNon the next successful
master-password check — no user action needed.