Releases: m4xx101/cryptex-oss
v2.8.1
First published release of the 2.8 line. 2.8.0 was tagged but never shipped: the release build hung on the emulated arm64 image and was killed at the job timeout, so no image and no GitHub Release were ever produced. 2.8.1 fixes the release pipeline and carries every 2.8.0 change.
Fixed
- Release pipeline no longer hangs on the emulated arm64 build. The multi-arch Docker build was a single
--platform amd64,arm64 --pushcommand; on tag refs its layer cache is cold, so arm64 rebuilt from scratch under QEMU emulation and stalled past the job timeout, taking the GitHub Release down with it. The build is now split:linux/amd64builds natively and publishes by digest unconditionally (image tags and the Release are cut from this leg alone), whilelinux/arm64builds best-effort under a hard time cap and is merged into the manifest only when it finishes. Per-architecture build caches keep repeat builds fast.
Note
- 2.8.1 includes all of the 2.8.0 work: the v2.7.0 structured-output experiment and its three mutators were reverted for a clean technique set, the Adversarial Suffix and Glitch-Token labs gained run-against-target panels, the model picker is scoped to your configured providers, the tool-title rendering bug is fixed, and a mount-smoke test now gates interactivity in CI.
Image
ghcr.io/m4xx101/cryptex-oss:v2.8.1(linux/amd64, pluslinux/arm64when the emulated build completes within its cap).:latest,:v2.8,:2.8track this tag.
v2.7.1
Critical fix. v2.7.0 (and, it turns out, v2.6.0 / v2.6.1) shipped a client-side regression that left the app non-interactive in production: the page rendered, but model pickers would not open, expanders would not expand, and runs would not start.
Fixed
- App non-interactive (an effect-update-depth loop crashed hydration). The Campaign front door (the home route) mirrored its goal into the shared cross-tool context with a reactive
$effect(sharedContext.goal = goal). That setter spreads (reads) the persisted context object before reassigning it, andcreatePersistedStatedid not dedup writes, so the effect depended on the very value it wrote and looped until Svelte aborted witheffect_update_depth_exceeded. The throw on mount of the home route wedged the Svelte client scheduler and left every interactive control across the SPA dead (the prerendered HTML still rendered, so it looked fine). Two coordinated fixes:app/src/lib/components/tools/campaign/CampaignTool.svelte: the goal-to-context mirror now tracks onlygoal, performs the shared-store read+write insideuntrack(), and skips no-op writes.app/src/lib/stores/_persisted.svelte.ts:createPersistedStatenow dedups structurally-equal writes, a class-wide guard so no future effect can infinite-loop on a persisted store.
- New regression test (
app/src/lib/stores/__tests__/persisted-no-loop.svelte.test.ts) drives the exact loop pattern through$effect.root+flushSyncand asserts it settles instead of throwing.
Process
- Root cause of the miss: prior release verification only confirmed pages rendered (a prerendered CSR shell always renders) and never tested interactivity. The release gate now adds a mount/effect regression test and a live "open the model picker" check.
Image
ghcr.io/m4xx101/cryptex-oss:v2.7.1(multi-archlinux/amd64+linux/arm64).:latest,:v2.7,:2.7track this tag.
v2.7.0
SOTA attack labs land, and the dead-on-frontier techniques get an honest Legacy flag (kept, never removed). The headline is a new prompt-level structured-output / control-plane attack lab, plus three 2025-2026 single-prompt mutators.
Added
- Structured-output attack lab (
/redteam/structured-output). Prompt-level BreakFun ("Trojan Schema", arXiv:2510.17904): an innocent extraction framing, a chain-of-thought distraction, and a schema the target auto-completes into compliance, so populating fields reads as a formatting task rather than a request for advice. Two kinds: Trojan Schema (4 schema surfaces, namely JSON-Schema draft-07, Python dataclass, TypeScript interface, and function-call args) and schema-coercion (a strict output contract where prose, hedges, and refusals are framed as schema-validation failures). Auto-pivot rotates kinds on refusal. Single-call test-against-target with a heuristic verdict (caveat preserved). 10 bundled MIT-licensed seed templates. - Campaign integration. A new
structured:*strategy family (one single-local adapter per kind) and a Structured-output (BreakFun) bundle, both auto-flowing into Full sweep. - 3 SOTA single-prompt mutators in PromptCraft and the technique registry (they auto-surface as Campaign strategies):
adversarial_poetry(constrained-verse rewrite, arXiv:2511.15304),bad_likert_judge(single-prompt Likert-calibration approximation, Palo Alto Unit 42; the full multi-turn flow is deferred to v2.9), andtrojan_schema(single-prompt BreakFun, sharing the lab's builder).
Changed
- Honest "Legacy" relabel pass (metadata only, nothing removed). Frontier models patched several 2021-2023 classes, and Cryptex now says so. The tab rail gains a
'legacy'status with an amber chip on AdvSuffix and Glitch; honesty lines were added to those tool descriptions, to theroleplayandsysprompt_extractmutator descriptions, and as a category note on/transformsand/decode(plain single-layer ciphers and encodings are reversible primitives, effective stacked as layers, not standalone frontier bypasses). Every technique stays fully functional as an educational primitive and a composable layer;red_team_personawas deliberately left unflagged (it remains a viable layer insidemulti_layer_attack).
Deferred
- True constrained-decoding (EnumAttack / DictAttack, arXiv:2503.24191) needs a gateway
response_formatpassthrough the BYOK chat gateway does not yet expose; it is sequenced for v2.9 behind that enabler. The "Toy to Tool" UX backbone (live dataset import, goal-by-model transfer matrix, JailbreakBench artifact export, OWASP / MITRE-ATLAS tagging) is the v2.8 milestone.
Tests
- 942 / 942 unit tests pass (906 baseline plus 36 new); 0 type-check errors.
Image
ghcr.io/m4xx101/cryptex-oss:v2.7.0(multi-archlinux/amd64pluslinux/arm64).:latest,:v2.7,:2.7,:v2,:2all point at the same SHA.
v2.6.1
Fixes the intermittent "a tool sometimes gets stuck / its list never loads, and a page reload fixes it" bug on the GitHub Pages site.
Fixed
- Stale-chunk hang after a deploy (root cause). Cryptex is a pure client-rendered SPA (
ssr = false,prerender = true) served from GitHub Pages and redeployed often. Every deploy rehashes the lazily-imported JS chunk filenames; a browser holding an older app shell then tries toimport()a chunk whose name no longer exists → 404 → the route never mounts → the tool stays blank → manual reload (fresh shell) fixes it. The non-determinism ("sometimes", "many tools", "reload fixes it") is the signature of this version-skew. Three coordinated fixes:app/src/hooks.client.ts(new): avite:preloadErrorlistener does a one-time guardedlocation.reload()the instant a chunk import 404s, so a stuck tool becomes a brief auto-refresh onto the fresh build instead of a dead end. Plus ahandleErrorclient hook that logs any other failure mode instead of letting it hang silently.svelte.config.js:kit.version.pollInterval = 60_000— SvelteKit polls_app/version.json(already unique per build) and flips theupdatedstore when a new deploy is detected.app/src/routes/+layout.svelte: abeforeNavigateguard forces a full-page navigation whenupdated.currentis true, so a client that already knows it is stale never attempts a dead client-side import.
- GitHub Pages deep-link 404. The static adapter fallback was
index.html, but GitHub Pages serves404.htmlfor unresolved paths. Changed the fallback to404.htmlso deep-links and no-trailing-slash URLs (e.g./cryptex-oss/emoji) fall through to the SPA shell and route correctly instead of hitting GitHub's own 404 page.
Changed
app/src/lib/workers/runInWorker.ts: added a 30s worker-reply timeout (defensive — terminates and surfaces a retryable error instead of awaiting forever if a worker ever loads but never posts back). Not implicated in the reported bug; belt-and-suspenders.svelte.config.js: removed the dead$legacy: '../js'alias (thejs/directory was deleted in v2.4.1).
Ruled out (investigated, not the cause)
- Web Worker URL resolution under the base path —
pool.tsuses the Vite-nativenew Worker(new URL(..., import.meta.url))pattern, which Vite rewrites to a correct base-aware hashed URL; workers also only engage for ≥50 KB inputs (never on tool-open). - Per-tool init / empty lists — Vault seeds load via eager
import.meta.glob(bundled, never fetched); no await-forever on open.
Tests
- 906 / 906 unit tests still pass; 0 type-check errors.
Image
ghcr.io/m4xx101/cryptex-oss:v2.6.1(multi-archlinux/amd64+linux/arm64).:latest,:v2.6,:2.6,:v2,:2all point at the same SHA.
v2.6.0
The toolbox becomes a pipeline. Cryptex now has a Campaign front door — one page where you type a goal, pick a target once, and the tool fans your goal across many attack strategies, judges each with an LLM judge, and shows a graded ASR report. This is the shape every serious red-team framework (garak, PyRIT, promptfoo, DeepTeam) converges on, built entirely on parts Cryptex already shipped. The 26 individual tools remain as power-user escape hatches.
Added
/campaign— the Campaign front door, now the home landing page. Type a goal + pick a target + pick a technique bundle → Run. Strategies fan out through a bounded worker-pool (3 at a time, so multi-turn orchestrators don't storm provider rate limits), each result is judged, and an ASR-by-strategy report streams in live with the winner highlighted, expandable payload/response/judge-reasoning per row, Export campaign JSON, Save winner to Vault, and Re-run vs another model (side-by-side via local snapshots). Cold-start aware: with no provider configured the page onboards (NoProviderBanner + "Configure a provider" CTA) instead of looking broken.- Strategy adapter layer (
app/src/lib/campaign/). The 26 tools collapse into three archetypes — single-local builders (reasoning kinds, SEAL cipher presets, Response-Attack priming styles, local-template mutators), single-llm techniques (registry mutators that generate via an LLM), and multi-turn orchestrators (TAP/PAIR/Crescendo/Many-Shot) — unified behind oneCampaignStrategy.run(ctx)contract. Six technique bundles: Quick, Reasoning models, Cipher stack, Response priming, Multi-turn orchestrators, Full sweep. Abliteration is deliberately excluded (it's a model-profiling suite, not a goal attack). - Shared campaign judge (LLM-default, heuristic fallback). Wraps the StrongREJECT scorer: LLM-as-judge by default, falls back to the regex heuristic on parse-failure / rate-limit, re-throws AbortError. Returns
bypassed | partial | refused+ score + reasoning. Heuristic, not the trained paper classifier — caveat preserved in the report. - Cross-tool context bridge. A new shared
cryptex.contextstore ({goal, targetModel}) plus aContextBridgecomponent wired into PromptCraft, Reasoning-attack, and Stacked-cipher: "Send to Campaign →" pushes a tool's goal+target into the front door, and "Use campaign goal/target" hydrates a tool from it. Explicit user actions only — never reactive, fully back-compat (no existing per-tool pref is touched).
Fixed
- Mobile: Guide, About, and Settings were unreachable. The v2.5.0 hamburger drawer listed only tools, and the Guide header link was hidden below the
smbreakpoint. Added a "More" section to the mobile drawer (Guide / About / Settings) and made the Guide button visible on mobile. The Settings sidebar no longer horizontal-scrolls (stacks vertically on phones).
Changed
- Home route
/redirects to/campaign(was/transforms). - TabRail gains a Campaign tab (first position, Rocket icon); it auto-propagates to the mobile drawer and the global running badge via the shared
tabsarray. - README, About page tool count (25 → 26), and the CI smoke-test (
/campaign/probe) updated.docs/USAGE.mdopens with a new Recipe 0: Campaign — one-shot red-team.
Tests
- 886 → 906 (+20): shared-context store (4), campaign judge (7), strategy adapters (6), campaign runner — bounded concurrency, error isolation, single history write (3).
Image
ghcr.io/m4xx101/cryptex-oss:v2.6.0(multi-archlinux/amd64+linux/arm64).:latest,:v2.6,:2.6,:v2,:2all point at the same SHA.
v2.5.0
Production release: pipeline polish, mobile-responsive shell, multi-tool visibility.
Fixed
docker pull :latestreturned stale code.:latestwas pushed from BOTH the tag-build job AND the main-branch build job in.github/workflows/docker.yml. A docs-only push tomainafter a release tag would race the tag job and overwrite:latestwith main's older HEAD. Removed the main-branch line;:latestnow ONLY tracks the most recent semver tag.- GitHub Releases stuck at v1.0.1. Tags
v2.0.0..v2.4.2existed in git but no workflow ever created Release entries. Addedsoftprops/action-gh-release@v2step gated on tag push; release body extracted from the matching[X.Y.Z]CHANGELOG section via awk. Every future tag auto-publishes a Release with formatted notes. - No URL printed on container startup. Added
docker-entrypoint.shthat prints a banner with the open URL before handing off to nginx. Honest about host-port mapping (doesn't print a misleadinglocalhost:80when usersdocker run -p 8080:80).
Added
- In-app version display. Footer chip shows
v{APP_VERSION}linking to the changelog. About page shows "Running v{APP_VERSION}" with View changelog + All releases links. Version is injected at build time via Vitedefine:reading the rootpackage.json— no hand-sync. - Update-available banner.
/settings/release/update-check.tsfetchesapi.github.com/.../releases/latestonce per session (6h sessionStorage cache) and surfaces a dismissible accent banner above the HeaderBar when a newer version exists. Per-version dismiss state in localStorage so dismissing v2.5.1 doesn't suppress v2.6.0. Silent on every failure path (network, 4xx, malformed JSON, CORS). Default ON; opt-out via the new "Release notifications" section in Settings → Local Data. - Mobile-responsive shell.
- New hamburger button (
<smonly) in HeaderBar opens a full-screenMobileNavDrawercontaining the same 26-tool list the desktop TabRail renders. Each row shows the sameanimate-pulserunning-dot — the "blinking effect" works on mobile. - Drawer accessibility:
role="dialog",aria-modal, focus trap, Escape to close, backdrop click closes, focus restores to the hamburger button. - Desktop TabRail wrapped in
hidden sm:blockso it disappears below 640 px (where it was wrapping to 3-5 rows and pushing the running dots off-screen). - Touch targets in HeaderBar buttons + Settings sidebar bumped to
min-h-11/h-10 w-10on mobile (closer to the 44 px HIG floor) and back to the desktop sizing onsm:and up. pt-safe/pb-safe/pl-safe/pr-safeutilities inapp/src/app.cssusingenv(safe-area-inset-*)— applied to the sticky header (iPhone notch) and the footer (Android nav bar).
- New hamburger button (
- Global "{N} running" badge in HeaderBar.
ActiveRunsBadge.sveltesurfaces in-flight runs from every tool, not just the currently-open route. Click opens a popover listing each running tool with elapsed time + a deep-link back to its route. Closes the visibility gap where users couldn't tell whether a long-running tool was still working after they navigated away. - Tool registry refactor.
TabRail.svelte'sTABSarray now carries atoolIdfield per entry inside a<script module>export.MobileNavDrawerandActiveRunsBadgeboth consume the same source-of-truth array. The old 9-entryhrefToToolId()switch is gone — all 26 visible tool routes now resolve to a toolId, so the desktop TabRail running-dot and the mobile drawer running-dot fire for every tool with anactiveRuns.start()registration. - Reload-orphan notification.
activeRuns.svelte.tswrites a tinySet<toolId>snapshot to localStorage on everystart()and removes on everyfinish/fail/cancel/clear. If the previous session left entries (closed tab, hard reload, crash mid-flight), the layoutonMountemits a one-shotnotify.info"N runs were interrupted by your last page reload" and clears the snapshot. The in-flight Promise can't be resumed, but the user's form state IS persisted (existinguseToolState). - Dynamic README badge.
README.mdline 21 swapped its hardcodedv2.3.0badge for ashields.io/github/v/releasedynamic badge that polls GitHub's API and serves a cached SVG. The README never drifts from the actual release surface again.
Changed
app/vite.config.ts: build now reads rootpackage.jsonviafs.readFileSyncand exposes__APP_VERSION__+__APP_REPO__as compile-time globals. Also dropped the stale$legacy: '../js'alias that pointed at the deleted directory.
Tests
- 873 → 886 (+13): full coverage for the new
release/update-check.tsmodule — semver compare edge cases, sessionStorage cache hit/miss, silent failure on non-OK responses, thrown fetch, malformed JSON, manual cache clearing.
Image
ghcr.io/m4xx101/cryptex-oss:v2.5.0(multi-archlinux/amd64+linux/arm64).:latest,:v2.5,:2.5,:v2,:2all point at the same SHA. The new pipeline guarantees:latesttracks the v2.5.0 SHA going forward — the bug that caused users to pull stale code is fixed.
v1.0.1
Bug-fix and feature release.
Highlights:
- Session persistence: long-running tools (probe-lab, cross-model-diff, harmbench, strongreject, jbb, replayer, fingerprinter, promptcraft, anticlassifier) now continue running when you switch tabs. The TabRail shows a pulsing indicator on tabs with active runs.
- 5 new providers: Ollama, LM Studio, vLLM, Llama.cpp (local servers) and NVIDIA NIM (cloud). All speak the OpenAI-compat protocol; configure in Settings -> Providers.
- 0 npm audit vulnerabilities (was 12). Vite, SvelteKit, and vitest upgraded to latest.
- Documentation cleanup: neutral framing throughout README and CLAUDE.md.
Hotfix (within hours of v1.0.1 release): Regenerated app/package-lock.json to fix 'npm ci' missing entries for react/react-dom/scheduler (transitive optional peers of the 'ai' SDK). Affected 'npm run build' from root and Docker builds.