Skip to content

Releases

Spernakit v3.47.6

Choose a tag to compare

@github-actions github-actions released this 21 Oct 03:45
7a3a0c6

Other changes

  • v3.47.6 - security fixes and derived-runner fix (#52) (7a3a0c6)

1 commit since v3.47.5.

Full Changelog: v3.47.5...v3.47.6

v3.47.5

Choose a tag to compare

@github-actions github-actions released this 20 Oct 02:04
55bfd7e

Documentation

  • release: v3.47.5 - runner-delivered gate, auth hardening, advisory fixes, site identity (#51) (55bfd7e)

2 commits since v3.47.4 (1 omitted as build/chore/ci plumbing).

Full Changelog: v3.47.4...v3.47.5

v3.47.4

Choose a tag to compare

@github-actions github-actions released this 28 Sep 19:39
84cc05a

[3.47.4] - 2026-09-28

Patch release. The notification bell shows only notifications, sign-out audit rows name the user
who signed out, the command palette builds its settings and profile entries from the same tab lists
the pages use, and the launcher waits for a slow backend instead of calling it a failure. The leak
guard gains a per-repository list of private names a public site may publish.

Fixed

  • The notification bell no longer fills with dashboard and file events. The WebSocket dispatcher
    now hands each channel handler the full message, and the bell acts only on messages of type
    notification.
  • Sign-out audit rows are attributed to the user who signed out. The session is resolved before its
    tokens are revoked, where previously the row was written after revocation and carried no user.
  • The command palette builds its settings entries from settingsTabs and its profile entries from
    profileTabs, so a derived app that swaps a tab no longer gets a palette entry for a route it
    removed and none for the one it added. The demo account buttons keep their labels on one line.
  • bun run start waits up to 30 seconds for each server, tells a process that exited apart from
    one still starting, and reports the actual failure. A cold backend start that took longer than
    the old 10-second limit was killed and reported as a failed start.
  • The fast qc subset that runs at commit time now says it ran 4 of the qc steps, so its success
    line no longer reads like a full smoke:qc pass.

Changed

  • The settings area's minimum role is one exported constant, SETTINGS_AREA_MIN_ROLE, read by the
    route guard and the command palette alike.
  • The commit-time leak guard reads an optional, tracked .githooks/leak-guard-publishes listing
    private names this repository is cleared to publish. The list is read from the staged index, an
    entry must match a private pattern in full, it never exempts the secret-format or home-path
    checks, and each use is announced. The template lists no names, so its behavior is unchanged.

Full Changelog: v3.47.3...v3.47.4

v3.47.3

Choose a tag to compare

@github-actions github-actions released this 26 Sep 18:25
9dd11ed

Patch release. The commit-time leak guard reads its private pattern file from the Windows
user-level setting when the environment does not carry it, and it now stops a commit when a
configured pattern file is missing or unreadable instead of passing with a warning. Dependencies
move to current versions and the formatting configuration converges with the fleet standard.

Fixed

  • The leak guard resolves LEAK_GUARD_PATTERNS from HKCU\Environment when the variable is not
    set, before falling back to ~/.config/leak-guard/patterns. A process started from a parent that
    predates the user-level variable never inherits it, and such a session previously committed with
    the private tier silently off. The seeder resolves the path the same way, so it cannot seed a
    second file at the default location.
  • A configured private pattern file that is missing or unreadable now stops the commit. Readability
    is judged by grep's exit status on the actual read, because a permission check can report a file
    readable when Windows access control denies the open. LEAK_GUARD_ALLOW_NO_PRIVATE=1 lets a
    single commit through deliberately. A machine with no pattern file configured at all still warns
    and runs the generic checks, so a freshly scaffolded project can make its first commit.
  • The hit message no longer suggests git commit --no-verify; a confirmed false positive is the
    user's call.
  • The scaffold copies of leak-guard.sh and leak-guard-setup.sh match the root copies again.

Changed

  • Production and development dependencies move to current versions: drizzle-orm 0.45.3,
    drizzle-kit 0.31.11, lru-cache 11.5.3, @tanstack/react-query 5.104.0, lucide-react
    1.48.0, @types/node 26.6.3, vite 8.3.1, eslint 10.11.0, eslint-plugin-jsdoc 64.5.4,
    eslint-plugin-perfectionist 5.12.1, knip 6.38.0, puppeteer 25.12.0, typescript-eslint
    8.70.1, and prettier 3.9.9, with the lockfile and license notices updated to match.
    eslint 10.11.0 matches aidd again.
  • The GitHub Actions workflows use actions/deploy-pages 5.0.1 and the current
    github/codeql-action/upload-sarif.
  • Line endings, indentation, width, and trailing commas agree across .editorconfig,
    .prettierrc, and .gitattributes. The *.ps1/*.psm1/*.psd1 CRLF exception, which
    contradicted the LF settings and was never in effect, is removed, five duplicated
    .gitattributes rules are dropped, and *.svg is treated as text so it diffs. Formatting output
    is unchanged.

Full Changelog: v3.47.2...v3.47.3

v3.47.2

Choose a tag to compare

@github-actions github-actions released this 17 Sep 19:24
fb26cae

Fixes

  • release: v3.47.2 - MFA token transport, upload signatures, secret redaction, and crawler route sampling (#44) (fb26cae)

1 commit since v3.47.1.

Full Changelog: v3.47.1...v3.47.2

v3.47.1

Choose a tag to compare

@github-actions github-actions released this 14 Sep 09:58
6e962b1

Fixes

  • release: v3.47.1 - harden authentication, secrets, and quality gates (#42) (6e962b1)

1 commit since v3.47.0.

Full Changelog: v3.47.0...v3.47.1

v3.47.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 18:45
951ee26

[3.47.0] - 2026-09-11

Minor release. Derived applications now detect quality checks missing from an overridden smoke
runbook. The fleet testing fixes cover authorization ordering, workspace errors, form state,
keyboard focus, validation messages, metrics, and health reporting.

Added

  • check:smoke-steps compares each application's smoke commands with its declared template
    version, including overridden runbooks. It reports missing template steps and commands with no
    package script. The check runs without caching so fetching a previously missing template tag
    makes the comparison take effect immediately.

Fixed

  • Every authorization guard runs before body validation, including workspace membership and role
    checks. Requests that should be refused no longer receive schema details first.
  • Workspace listings return 404 for an unknown workspace, and adding a member returns 404 for an
    unknown user. Unauthorized callers continue to receive the same refusal regardless of existence.
  • Editing a workspace preserves typed names. Dialog forms remain mounted while a submission is
    pending, and closing a dialog opened from a table row returns focus to that row's menu button.
  • Rejected URL filters leave the page usable with an error message and are not retried.
  • Username validation agrees between the browser and API. Password rechecks identify the current
    password as the one refused, and union validation names the accepted values without echoing input.
  • Authentication settings display the pre-boot rate-limit kill-switch alongside the editable
    setting. Tables display their actual page size, and onboarding links to the password form's page.
  • Dashboard history spans the requested interval. Memory health checks use the process's available
    memory limit and configured thresholds.
  • The create-user dialog has a complete description. The stylesheet no longer opts into
    cross-document view transitions.
  • The Vite proxy connects to the address family the backend binds instead of relying on localhost.
  • Documentation checks reject links to gitignored files, which are unavailable in a fresh clone.
  • Error-log verification waits for the logging transport to flush and the probe to finish within
    its existing timeout, fixing failures on a cold dependency cache.
  • The current-password regression check provisions its own temporary MFA keys, so it exercises
    password rejection in a fresh checkout as well as on a configured workstation.
  • Container license inventories record package names and licenses without pinning live Alpine
    package versions. Each built image retains and verifies its exact installed-version inventory.

Changed

  • Updated React and React DOM to 19.3.0, Vite to 8.3.0, lucide-react to 1.45.0, nodemailer to
    10.0.6, the React type packages to 19.3.0, and eslint-plugin-jsdoc to 64.3.9. The lockfile and
    generated license notices follow those versions.
  • Regenerated frontend size budgets from the React 19.3 production build. A controlled comparison
    with the prior React runtime measured 29,324 additional JavaScript bytes, 7,161 additional
    first-load Brotli bytes, and 7,899 additional first-load JavaScript gzip bytes. The critical-path
    budget retains its 2 KB allowance; React preload and import-waterfall checks remain enforced.

Full Changelog: v3.46.0...v3.47.0

v3.46.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 18:37

Features

  • release: v3.46.0 - verifiable release screenshot captures (#36) (65246a1)

Fixes

  • release: make a release capture verifiable end to end (#37) (0f62393)

2 commits since v3.45.0.

Full Changelog: v3.45.0...v3.46.0

v3.45.0

Choose a tag to compare

@github-actions github-actions released this 28 Aug 07:54

Minor release. It carries the findings the v3.44.3 fleet sweep produced across all ten derived
applications, plus three new capabilities that came out of them. The largest change is structural:
authorization now runs before body validation on every route, and a new gate proves that every route
which does not refuse an anonymous caller is on a list that says why.

Added

  • The audit log can be filtered by outcome. GET /api/v1/audit takes outcome=failed or
    outcome=succeeded, and every row carries the status the writer recorded and the username the
    request named. A rejected sign-in never authenticates, so those rows used to render as System
    and were indistinguishable from background activity; the viewer now shows the account that was
    attempted and the status as a badge beside the path. Both fields are reachable through field
    selection, so an operator can alert on a burst of failures.
  • A dashboard share link can be revoked. DELETE /api/v1/dashboards/:id/share clears the token and
    its expiry, and GET on the same path reports whether a link is live and when it stops working.
    Revoke deliberately ignores the sharingEnabled setting, because turning sharing off must not
    strand links that were issued while it was on. The Share dialog shows the current state and offers
    Revoke or Create new link.
  • A bug report can name the report that supersedes it. PUT /api/v1/bugs/:id/superseded-by sets the
    link and null clears it, open to the reporter as well as to an administrator. Self-links, cycles
    of any length, and successors naming no report are refused before anything is written, and the
    link never touches either report's text or status. The default inbox leaves superseded reports out
    of its rows and its total, and takes includeSuperseded=true to bring them back.
  • test:public-route-surface joins smoke:qc. It boots the application, enumerates every route
    Elysia registered, and sends each one an anonymous request. Anything that answers has to appear in
    scripts/lib/public-routes.ts with a written reason. Authorization travels as a route option, so a
    route ships open by leaving requireAuth off and nothing looks wrong in the file; three route
    modules hold guarded and unguarded handlers side by side, which is why this asks the application
    rather than reading the source. The list is checked in both directions, so an entry naming a route
    that no longer exists, or one whose route is guarded now, fails rather than sitting there as an
    exemption nobody is using.

Security

  • Authorization runs before body validation. Elysia validates a request body against the route schema
    before it calls beforeHandle, so guards living there ran too late: an anonymous caller who posted
    a malformed body to a protected route was answered 400 with the field names and constraints of a
    route they were never allowed to reach, and every rejected request had its body parsed and checked
    before anyone asked who sent it. The guards now run at the transform stage. They travel as
    requireAuth, requireRole and requireSelectedWorkspace route options carried by macros on the
    plugins every route file already uses, so a route added later gets the ordering by writing the
    option rather than by remembering a lifecycle rule. The shared-dashboard rate limiter moved with
    them for the same reason. Converting the routes also fixed a latent bug: the old composite form
    built a throwaway context object, so the role requireRoleFresh read back from the database never
    reached the handler.
  • The workspace guards answer 404 for a workspace that is not there. A soft delete leaves the
    membership rows behind and the membership queries do not join workspaces, so a member of a deleted
    workspace passed every check: the member roster answered 200 with the full list, and adding and
    removing members still succeeded. The existence check now runs last in each guard, so a caller who
    was going to be refused hears 403 for a real workspace and for an absent one and the status cannot
    be used to learn which ids exist.
  • config/example.json no longer excludes localhost from the audit log. It shipped
    audit.ipWhitelist as ["127.0.0.1", "::1"], which is exactly the state the audit plugin's own
    comment says must never happen, so a fresh clone started with no audit trail for anything reaching
    it locally. The list is empty now, and check:config fails on any loopback entry, matching the
    whole 127.0.0.0/8 range, the ::ffff: mapped form and the name localhost. An address an
    operator lists in config/<slug>.json is still honoured.

Changed

  • The workspace header has one owner and one answer. A caller who left X-Workspace-ID off was told
    different things by different routes, in two spellings of the header name, neither matching what
    the CORS allow-list publishes or the client sends. backend/src/guards/workspaceHeader.ts now owns
    the header name, the missing-header message, a distinct message for a header that could not be
    read, and the not-found answer. The scope rule is now plain: a listing follows the header whoever
    sent it, and only a request naming no workspace gets the cross-workspace view. Previously the scope
    was decided by re-reading the caller's role, so a SYSOP who named a workspace had that id discarded
    and was answered with every workspace instead of the one they asked for.
  • A 404 is treated as an answer rather than a failure. queryClient.ts decides that in one predicate
    that both the retry rule and the throw rule consult, so a page can render its own not-found state
    instead of waiting through three retries and then handing an unnamed failure to the error boundary.
    A page added later inherits this without opting in; everything else still throws.
  • The multipart transport ceiling now sits above storage.maxFileSize. Both shipped at 10 MB, so a
    file at exactly the file limit exceeded the transport limit once the multipart envelope was added,
    and Bun answers that with an empty 413 rather than the message the route would have given.

Fixed

  • An interrupted view transition no longer raises an unhandled rejection. The template opts every
    navigation into a cross-document transition, and navigating again inside the 220ms animation skips
    the running one, which rejects its promises with an AbortError that nothing held. Clicking twice
    on any page produced a console rejection, and a crawler navigating faster than the animation
    produced one on every page. frontend/src/lib/viewTransitions.ts holds those promises and absorbs
    only an AbortError, so a transition that failed for another reason is still reported. The CSS is
    untouched: same rule, same timing, same reduced-motion path.
  • A bug report that is empty once trimmed is refused. The route checked minLength against the
    description exactly as it arrived and the service trimmed it afterwards, so a description of
    nothing but spaces was stored as a row titled (untitled) with an empty body, which could then
    never be corrected or removed. The route trims in a transform hook now, so the value the schema is
    asked about is the value that gets stored, and a padded email address is accepted rather than
    refused for padding that would have been removed anyway.
  • GET /api/v1/bugs answers. The listing registered its path with a trailing slash beside the POST's
    empty string, which puts two nodes in the router and resolved a GET of either URL to the node the
    POST created, so both spellings returned 404.
  • Opening a dashboard that no longer exists shows the not-found state. It previously showed a
    skeleton for about seven seconds and then a failure that named nothing. The same was true of a
    shared dashboard whose link had expired.
  • The onboarding checklist reports the sysop password change instead of affirming it. Its first step
    was hard-coded complete and signing in was what made it true, so the checklist claimed a password
    change nobody had performed, on the one account whose default password is published in the
    project's own documentation. Signing in and changing the password are now separate steps, and the
    second reads the seeded SYSOP row so it closes only when the account's holder actually changes it.
  • Application errors reach logs/<name>.error.log. That file is the spawned server's stderr, but the
    logger sent every level to stdout, so it could only ever hold crashes and interpreter warnings, and
    every tool that read it to judge a startup was reading a file that could not hold an application
    error. Errors are copied to stderr rather than moved, so the main log still carries every level.
    Transport targets are resolved to absolute paths, which fixes a logger loaded from outside
    backend/ dying with "unable to determine transport target for pino-pretty".
  • A binary upload is no longer rejected as a long text line. The line-length check ran over every
    buffered upload before the switch that chose a text-format validator, so a binary body with no
    newline in it was refused with "Line 1 exceeds maximum length of 10000 characters". It now runs
    only for a MIME type that has a content validator.
  • A fractional page or limit is refused. Every paginated route declared them as t.Numeric with
    no integer constraint, so ?page=1.5 became a fractional SQL LIMIT/OFFSET: a driver error on
    some backends and a silently misaligned 200 on others. Both parameters now come from
    backend/src/schemas/pagination.ts and are declared on t.Integer. A value outside its range also
    reported a validation failure naming a property that exists on no schema; the message names the
    parameter the caller actually sent. Reported by nine of the ten derived applications during the
    v3.44.3 sweep.

15 commits since v3.44.3, squashed into 71fc77b (#32).

Full Changelog: v3.44.3...v3.45.0

v3.44.3

Choose a tag to compare

@github-actions github-actions released this 27 Aug 21:46

Refactoring

  • branding: drop the normalizer branch for a deleted test file (#30) (f7cd68b)

2 commits since v3.44.2 (1 omitted as build/chore/ci plumbing).

Full Changelog: v3.44.2...v3.44.3