| 2026-10-02 |
Bearer auth fails closed: a dead token no longer rides the session cookie
...
Presenting an invalid or revoked token used to fall through to the
session cookie in the same request, silently authenticating as
channel=ui with the session's privileges — a revoked token kept
"working" inside a logged-in browser and masked revocation (and the
extension with it). Now a Bearer header is final: a token that does not
authenticate answers 401; cookie auth is only for requests without one.
Regression test added.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
2 hours ago
|
Session death drops the SPA onto the sign-in screen without a reload
...
Any 401 from any API call fires gncreds:unauthenticated; the App listens
and switches to the internal sign-in card immediately, so a finished
session (global auth logout, TTL, revoked session) is visible at once
instead of letting the app look alive while every request fails. api.js
also stops retrying 4xx answers — only network errors and 5xx are
retried, so a dead session is not re-sent three times with backoff.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 hours ago
|
Webhook: accept trailing-slash target, 401 on bad signature, global_logout ends sessions
...
POST /webhooks/gnexus-auth/ (the spelling the auth platform had stored)
never redirected: the GET SPA catch-all matched the path and answered
405 allow:GET, so the platform's auth.global_logout deliveries kept
retrying and creds sessions outlived a global logout. Register both
spellings, return 401/400 on signature/payload failures instead of an
unhandled 500, and on auth.global_logout delete every session row of the
targeted user (other users' sessions untouched; API tokens long-lived
by design and revoked explicitly).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 hours ago
|
Revoked tokens stop being invisible: popup plate + token ID, tokens table columns
...
Extension 0.2.2: a 401 from any API call flips a tokenInvalid flag that the
popup renders as a dedicated plate (same load, via status propagation from
the background, plus the pre-flagged check on popup open); the Settings
drawer shows the stored token's public ID so it can be matched against the
tokens table. Web: Tokens tab gains Public ID / Created / Last used columns.
Design pack v24 ships the new strings (en/uk/ru); served zips rehashed.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
3 hours ago
|
Splash over the empty shell on sign-in; extension v0.2.1 catches submit() logins
...
- App.vue gates on auth state: 'checking' renders a logo + spinner card
until /me answers, so the shell never flashes empty before the
sign-in screen appears; 'denied' -> sign-in card, 'ready' -> app.
- content.js: DLE-style <button onclick="submit();"> calls
form.submit(), which fires no submit event - the login form's
interceptor stayed silent (animaunt.org regression). Capture happens
on the submit-button click and Enter too; duplicate captures within
1.5s with identical credentials are dropped. Verified end-to-end on
animaunt.org: modal login -> popup save card -> vault entry.
- Release 0.2.1: Makefile + extension manifest bumped, zips rebuilt
and hashed into the deploy manifest for the download page.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
12 hours ago
|
Logout lands on the internal sign-in screen, no SSO auto-login
...
Nav-style flow: POST /auth/logout then reload the app — /me answers 401,
so the SPA shows its own sign-in gate (logo card with one button) instead
of bouncing to /auth/login, which silently re-authenticated through the
still-alive gnexus-auth session. OAuth now starts only from the explicit
"Sign in with GNEXUS" click; a non-401 failure on load keeps the old
redirect.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
13 hours ago
|
Serve 0.2.0 extension builds from the download page
...
extensions/manifest.json advertised the 0.2.0 filenames while the served
extensions/dist still held the 0.1.1 zips — the page filtered unknown
files and shipped an empty build list, so download buttons never
appeared. Ship the current 0.2.0 builds and refresh their sha256/size,
drop the 0.1.1 zips.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
14 hours ago
|
Settings page hosts the language switcher; profile-edit modal removed
...
The interface-language GnSelect now lives on the Settings page (Auto /
English / Українська / Русский) and applies on change, so the Edit-profile
modal — and the pencil button that opened it — are gone. The dead profile
slugs leave the dictionaries.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
14 hours ago
|
Web app: en/uk/ru localization, language picked from the backend
...
Tiny reactive i18n module (locale ref, t() with {param} interpolation and
ru/uk plural forms, flat per-area dictionaries, en fallback) instead of a
vue-i18n dependency. App.vue and SecretDetailPanel.vue migrate every
hardcoded string to message slugs; /me's locale_effective (settings
override -> auth account -> en) seeds the language on load, the profile
modal gains a language GnSelect (Auto/English/Українська/Русский) that
PATCHes /me and re-applies the effective locale, and <html lang> follows.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
14 hours ago
|
Extension: localized strings ship in the design pack, UI language from the backend
Eugene Sukhodolskiy
committed
15 hours ago
|
Per-service locale override: effective locale chain, PATCH /me validation
...
users.locale is now purely the per-service override chosen in settings;
the auth-derived default lives in users.profile.locale and is never
written back over the override by login or the webhook. PATCH /me
normalizes language tags (en-US -> en), accepts auto to clear, and
rejects unsupported locales with 422. UserRead gains locale_effective
(override -> profile -> en) which the web UI and the extension use as
their UI language. The validation error handler strips non-serializable
pydantic ctx.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
15 hours ago
|
Mobile audit: collapse table into event cards with wrapping metadata
...
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
15 hours ago
|
Mobile secrets rows: single-line list without the status badge
...
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
16 hours ago
|
Compact mobile secrets rows: title line plus status/chevron line in one grid card
...
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
16 hours ago
|
UX fixes: pagination ellipsis, mobile layouts, tokens table, danger-zone radius
...
- Pagination re-collapses long page runs to "1 2 3 … 22" (AppPagination, same
look as the kit's) for secrets, audit and admin users.
- Settings metrics: grid columns; metric cards stretch to the full track and
stack one per row on mobile.
- Danger zone gets the standard 6px border radius.
- Revealed fields: one column on mobile, long values wrap instead of pushing
the page into horizontal scroll.
- Secrets table on mobile becomes a card list: category stacks under the
title, purpose column drops out, thead hidden.
- API tokens list is now a full-width table (Name / Scopes / actions).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
16 hours ago
|
Serve extension design resources from the server as a versioned pack
...
CSS (card + popup), field-detection keyword tables and UI config now ship as
extensions/pack/ with sha256 in pack.json and a monotonic version. The backend
serves pack.json unauthenticated (no-cache) and files versioned/immutable; a
background-worker sync validates schema, ext-version compatibility and hashes,
then stores the pack atomically in chrome.storage.local — content scripts and
the popup only read storage, never fetch, and fall back to the bundled
defaults. Publish tool auto-bumps on content change and writes pack.json last.
Firefox bundle inlines pack-shared.js; release 0.2.0.
E2E (15/15): adoption, live restyle without re-release, rollback and
min-extension guards, sha-mismatch abort, offline fallback. Tests: node 25+21,
pytest 57.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
16 hours ago
|
| 2026-10-01 |

Add PWA shell and a client-side biometric gate on secret reveal
...
PWA: hand-rolled service worker (public/sw.js, no build plugin) with a
precached shell, network-first navigations with a 4s timeout and an
inline offline page, and stale-while-revalidate for /assets and public
files that byte-compares in the background and replaces an entry only
when the server copy differs. API/auth/MCP/webhook traffic is never
intercepted or cached — cookie and secret responses stay out of cache
storage. manifest.webmanifest + theme-color + apple-touch-icon; icons
rasterized from the shield logo by tools/rasterize_icons.py (192/512,
any + maskable). SW registers in main.js under the same
secure-context condition the gate uses, with silent background update
ticks on visibility/online/15min.
Biometric gate (frontend/src/biometric.js): a platform-authenticator
passkey (Android fingerprints through BiometricPrompt, Touch ID,
Windows Hello) is enrolled opt-in from Settings; every reveal and
version reveal runs a fresh WebAuthn assertion with
userVerification:"required", whose clientData/authenticatorData/
signature are verified offline in the same browser against the stored
public key. The server never learns about biometrics — this is an
interface lock, not encryption. Disable requires the same proof, and
the toggle disables itself with an explanatory note on devices
without a platform authenticator.
Also fixes a pre-existing bug the verification run caught: the detail
panel's Hide button wrote `revealed.value = null` in the template,
where refs auto-unwrap, so the panel never returned to masked state.
Verified end to end with a CDP virtual authenticator: 16 checks cover
manifest/SW/offline shell/freshness/no-API-cache, enrollment, reveal
through the gate (HTTP 200), reveal blocked with no sensor, disable
refused without the sensor and allowed with it, and the disabled
toggle + note on a device without biometrics.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

Detect login fields with a scoring classifier instead of heuristics
...
field-detect.js is a pure UMD module: every input becomes a descriptor
(name/id/placeholder/aria-label/label text, visibility state, type,
autocomplete token) scored against multilingual keyword tables with
anti-keywords for traps (search, newsletter, cc, otp and now repeat-
password fields). findLoginTargets pairs one username with one password
per form context, merges confirm-password pairs, and falls back to the
closest preceding input in anonymous SPA forms.
content.js reduces to DOM probing (descriptor building + scan) and the
card/interceptor flow; the manifest loads field-detect.js first.
The module is unit-tested without a browser via tools/test-field-detect.js
(16 plain-node cases over a fake tree) and verified end-to-end in Chromium
on a page mixing russian placeholders, newsletter/search/otp/cc traps and
a confirm-password signup form: three cards, correct pairs, Use fills the
right fields only.
Makefile: the per-file $(SRC) replaces the directory prerequisite (a
stale edit inside src/ never rebuilt the bundles), recipes copy with
cp --parents, and make test runs the node suite.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|

Render the autofill card in a closed Shadow DOM
...
The card used to be injected straight into the page: host-page CSS could
restyle it (or hide/mimic it), page JS could read the card's DOM — leaking
secret titles — and reach its buttons via querySelector on our class names.
Now it lives in a closed shadow root on a bare sized-to-zero host element:
- CSS does not cross the shadow boundary in either direction, so page styles
can no longer repaint the card;
- the page cannot traverse into a closed root (host.shadowRoot is null), so
secret titles and buttons are unreachable from the main world;
- src/content.css is no longer injected into page stylesheets — it is now a
web-accessible resource, fetched once per page and injected as a <style>
inside the shadow root (with a graceful no-styles fallback).
- while restyling the card, bring it onto the gnexus-ui-kit 1.0 palette
(panel #16161e, left accent border, uppercase IBM Plex Mono titles) to
match the rebuilt popup.
Verified end-to-end in real Chromium: isolation probe reports a bare empty
host, null shadowRoot, no card nodes or CSS rules reachable from the page,
no title leak under deliberately hostile page CSS (color:red !important over
all divs); clicking Use fills the form and removes the card.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Polish custom views on kit 1.0: timeline, audit chain, modals
...
- Audit: the GnEmptyState's v-else bound to the GnPagination's v-if, so the
phantom "No audit events" block rendered alongside a populated table; move
pagination outside the loading/rows/empty chain and gate it explicitly.
- Timeline: spell out the text fallback inside the per-item slot (a truthy
dynamic slot always overrides GnTimeline's item.text), humanize created_at
to local "YYYY-MM-DD HH:mm" in history and audit, pluralize the field
count, and draw a connector rail between markers.
- Modals/toasts: drop the app-side a-show/a-hide keyframes — kit 1.0 owns
both transitions itself; align create-modal spacing to the kit's 15px
rhythm and add a hairline separator between modal sections.
Verified with Playwright screenshots (history, audit, create modal
open/close/reopen) on the live server; no page errors.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Release extension 0.1.1: kit 1.0 lib, shield logo icons
...
Rebuild both browsers' packages against gnexus-ui-kit 1.0 (make lib
from frontend/node_modules) and swap the generic dots mark for the
shield keyhole logo rasterized from frontend/public/logo.svg
(icon.svg + 16/32/48/128 PNG rsvg-convert). Popup verified in
chromium: settings drawer and secrets list render with no page errors.
Version bumped in extension manifest + Makefile; extensions/manifest.json
points at the 0.1.1 zips with fresh sha256/size (0.1.0 zips removed).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Import browser extension source under extensions/extension
...
Move the gnexus-creds-extension sources (Chrome/Firefox MV3) from the
separate sibling repository into extensions/extension/ so builds live
next to the packaged releases this service distributes
(extensions/dist/ + extensions/manifest.json, untouched).
- add a reproducible `make lib` target materializing lib/ from the
frontend's pinned gnexus-ui-kit (gitignored, like before)
- drop the playwright-only package.json/lock; system node+zip suffice
- scoped .gitignore so it cannot shadow tracked extensions/dist zips
- README (main + extension): document the in-repo build and publish flow
Rebuilt zips differ from released ones only in the three lib files
(older kit in the release); extension code itself is byte-identical.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Adopt shield keyhole logo, serve dist public assets from SPA fallback
...
gnexus-creds now has its own mark: a shield with a keyhole, drawn in the
gnexus-ui-kit language (dark tile, line-art strokes, square keyhole
terminal in the role color). Ship it as frontend/public/logo.svg, use it
in the navigation shell instead of the external gnexus.space PNG, and
register it as the SVG favicon.
The SPA fallback in main.py returned index.html for every unknown path,
so /logo.svg rendered the HTML shell instead of the image; serve real
files that exist in frontend/dist first and fall back only afterwards.
Also drop two unused imports flagged by ruff.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
Update gnexus-ui-kit to 1.0.0, drop vendored GnModal, use kit tokens
...
Upgrade the git dependency to 1.0.0 (allow-git for npm 12, recorded in
frontend/.npmrc). The vendored GnModal.vue copy is no longer needed: the
kit's overlay transitions are fixed upstream via useOverlayTransition,
and the official component is API-identical. Replace the five hardcoded
palette hexes in styles.css with kit design tokens so theme swaps stay
one import away.
Also restate the form-group hint styles for .tag-input: GnTagInput
renders its hint outside .form-group, so the scoped .form-group
.input-info styles from kit 1.0 miss it and the hint was glued to the
field with no top margin.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-08-25 |
Refresh extension builds (split Chrome/Firefox)
...
Repackage the Chrome and Firefox builds from the extension repo and
update extensions/manifest.json checksums, sizes, and release date.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 Aug
|
| 2026-08-24 |
Remove empty Install section from extension page
...
The install instructions block added no value and rendered empty;
drop it, keeping the build download cards and the API-token action.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 Aug
|
Pin mcp<2 to avoid fastmcp import breakage
...
mcp 2.0.0 removed mcp.server.fastmcp, breaking the import in
mcp_protocol.py at startup and causing uvicorn to crash (502 on all
pages after a clean image rebuild). Pin to >=1.27.1,<2 so the resolved
mcp 1.27.1 keeps the working FastMCP API.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 Aug
|
Add in-app extension download page
...
Distribute the gnexus-creds browser extension from the service itself.
Package Chrome and Firefox builds into extensions/dist/ with a manifest
describing version, checksums, and sizes; serve them via authenticated
FastAPI endpoints and surface a new Extension tab in the Vue UI with
download cards, SHA-256, and install instructions.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 Aug
|
| 2026-05-28 |
Bump frontend version to 0.2.0
Eugene Sukhodolskiy
committed
on 28 May
|
Style fixes: timeline hover, page-header layout, toolbar sort icon
...
- Disable timeline-card hover background/transform
- Fix page-header-compact: overflow initial, content row layout
- Move sort dropdown out of toolbar into actions row
- Replace sort button label with icon-only GnIconButton
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 28 May
|