v0.2.70 — Data-protection hardening, self-hosted docs, provider passthrough
This release lands a 14-phase data-protection and retention hardening cornerstone (legal hold, audit-chain checkpoints, RAG bearer auth + SSRF guards, GDPR Art. 17 erasure end-to-end, per-category retention floors), replaces Mintlify-hosted docs with a self-hosted tale-docs service that shares chrome and i18n with the marketing site, and adds providerOptions passthrough so OpenRouter quantization, OpenAI service_tier, Together safety_model, and similar provider features can be configured without code changes.
🔒 Security
- Data-protection & retention hardening (14 phases) — opt-in RAG bearer auth (
RAG_AUTH_TOKEN), 169.254.0.0/16 + 0.0.0.0/8 + fc00::/7 CIDR blocklist + cloud-metadata hostname blocklist on RAG_URL (with redirect: 'manual' to block IMDS pivots), audit-log HMAC-SHA256 chain with checkpoint rows, env-tunable per-category retention floors (audit min hardcoded at 365d), legal hold with maker-checker release flow, cascade-correct thread deletion, RLS gap fix for trashed/expired threads (#1676)
- Provider host policy + developer-settings gate — IMDS hard-block + RFC1918 opt-in (
TALE_ALLOW_PRIVATE_PROVIDER_HOSTS) on provider base URLs, developerSettings ability gate on all provider mutations/probes, secrets-first delete order, audit logging on provider changes (#1694)
- Quantization variant validation — agent JSON
@quant suffixes validated against declared quantizations; bogus variants rejected with UNKNOWN_MODEL_VARIANT (#1697)
🤖 Model & Provider
providerOptions passthrough — provider- and model-level JSON forwarded verbatim into LLM request bodies (OpenRouter quantization/routing, OpenAI service_tier, Together safety_model); Zod deny-list blocks body-overwrite keys (model, messages, max_tokens, …) (#1694)
- Quantization variants as first-class selectable models — each
quantizations entry shows up as its own pickable model (e.g. GLM 5.1 FP8 vs GLM 5.1 FP4) in chat, arena, and agent editors; encoded as provider:base@quant end-to-end (#1697)
- Catalog refresh — GLM 5.1 / 5-turbo / 5v-turbo, Kimi K2.6, Qwen 3.6 max-preview/plus/flash/35b-a3b, Gemma 4 31b-it / 26b-a4b-it added to OpenRouter (#1694)
- OpenRouter model list, prices, and pricing adjustments refreshed (multiple chore commits)
nvidia/nemotron-3-super repointed to the live OpenRouter id nvidia/nemotron-3-super-120b-a12b; pricing aligned with the live API
🚀 Features
- GDPR Art. 17 admin UI — self-serve data-subject-requests page under Settings > Governance, structured
reasonCode capture, single-grant Art. 12(3) extension (1–60 days), SLA countdown badge, audit timeline, inline legal-hold block panel (#1696)
- Self-hosted docs service — Mintlify Cloud replaced by
services/docs/ (TanStack Start), shared @tale/webui chrome with the marketing site, Mintlify-compatible MDX components, ⌘K search, per-page .md endpoints, /llms.txt + /llms-full.txt, AI page-actions menu (Open in ChatGPT/Claude/Cursor) (#1680)
- Server-side locale detection + cross-subdomain cookie redirect — first-time
de/fr browsers get 302'd to /de / /fr and a tale_locale cookie is set; cookie domain configurable via LOCALE_COOKIE_DOMAIN so tale.dev and docs.tale.dev share the preference (#1690)
- Legal pages moved to marketing site with PDF downloads — privacy-policy, terms-of-service, DPA, personalization now at
/legal/<slug> with a Playwright-generated dist/<slug>.pdf per locale (#1679)
- Pricing-page user-count slider — 25–1000 with manual override; per-user breakdown on the Enterprise card (#1677)
- Standalone
tale-web / tale-docs container publishing — both sites get independent compose stacks and ship to ghcr.io/tale-project/tale/tale-{web,docs} on tagged releases; Discord webhook replaces Microsoft Teams for marketing form submissions (#1683)
⚡ Performance
- Docs cold-load cut ~60% —
mermaid-vendor (719 KB gzipped) stripped from the entry preload graph and lazy-loaded only when a Mermaid diagram is on the page; ~30 individual lucide-react icon chunks coalesced into a single lucide-vendor chunk (#1689)
🛠 Improvements
- Multi-language provider/model descriptions —
resolveProviderLocale + resolveModelLocale mirror the agent-resolver fallback chain; per-locale tab editor in the provider edit panel; every model description in examples/providers/*.json translated into de + fr
- Provider models search + UX polish — search input + empty state on the provider detail page (filters id, display name, description, tags — raw + localized); separate "Default models" panel; "Webhook" renamed to "Workers" in agent settings (#1693)
@tale/markdown package extracted — chat, docs, and marketing share one Shiki singleton, one component set, and one styling pass; streaming primitives (incremental reveal, mid-stream repair, CJK plugin) move in; fixes code-block line-number drift on trailing-newline blocks and Mermaid parse-error fragment accumulation (#1692)
- Docs URL configurable —
WEB_DOCS_URL, DOCS_BASE_URL, DOCS_SITE_URL Dockerfile build args let docs mount at any path on any host; WEB_DOCS_URL now applies to the mobile-nav button and dist/robots.txt (#1684, #1685, #1686, #1687)
- Toast positioning + automations table polish — top-center toasts for auth flows (log in, sign up, 2FA, forgot-password); automations table replaces
version column with localized created date; raw <select>/<input> migrated to shared Select / Input / SearchableSelect; click-to-toast hint replaces forgot-password helper text (#1678)
🐛 Fixes
- Prefixed-locale routes +
/docs redirects — services/web switches base: './' → base: '/' so the SPA shell is self-contained at any depth (fixes blank /de/pricing after locale rollout); services/docs now prepends DOCS_BASE_URL to the Location header so Caddy-mounted /docs no longer redirects to the wrong origin (#1691)
- Web header/footer styling restored +
/llms{,-full}.txt served in dev — globals.css now imports @tale/webui/globals.css so its @source directive picks up shared layout utilities; LLM artifacts written into public/ so Vite dev serves them (#1682)
💥 Breaking Changes
de-AT and fr-CH locale tracks removed — only de, fr, and de-CH remain across @tale/webui, services/{platform,docs,web}, agent terminology, and the plop service template. Agents and user profiles previously pinned to de-AT or fr-CH should be re-targeted to de or fr (#1682)
- Audit-log retention floor enforced at 365 days — orgs with
auditLogRetentionDays < 365 are runtime-clamped to 365 with a one-time warn log per org; explicit re-configuration via the new bounded retention editor required to silence the warning (#1676)
📝 Other
- CI builds container images once and reuses them across smoke/validate/scan jobs (#1695)
- All CI jobs moved to free GitHub-hosted runners (#1674)
- Design file refresh: AI providers, personalization, integrations panels (#1681)
Migration Guide
Most operators only need:
tale upgrade
tale deploy
If you fall into any of the cases below, take the corresponding action before or right after tale deploy:
1. Enable RAG bearer auth (recommended for production)
The RAG service gains opt-in Bearer-token auth. Behavior is presence-based — no token is baked into the image:
- Unset (default): RAG accepts unauthenticated requests on the container network; both RAG and platform log a one-time
[SECURITY] RAG_AUTH_TOKEN unset warning at startup. Existing deployments continue to work without changes.
- Set: Every platform → RAG request carries
Authorization: Bearer ${RAG_AUTH_TOKEN}; RAG rejects mismatches with 401.
For production posture, set the same value on both containers:
# In your .env or compose override (same value on both)
RAG_AUTH_TOKEN=<a long random secret>
The token is re-read from the env on every call, so rotation takes effect on the next request without a process restart.
2. RAG_URL private-host check
RAG_URL is now validated against the IMDS (169.254.0.0/16) and the "this network" (0.0.0.0/8) blocklists at platform startup. If your RAG_URL points to anything in those ranges (it should not under any normal deployment), update it to a routable internal address before deploying.
3. Audit-log retention floor (operators with auditLogRetentionDays < 365)
Orgs with auditLogRetentionDays under the new 365-day floor will be runtime-clamped on first read and a warn log emitted. To fix permanently, open Settings > Governance > Retention and re-save with a value >= 365 (the editor now enforces bounds and shows the floor inline).
4. de-AT / fr-CH locales removed
If any agents, users, or org-level defaults were pinned to de-AT or fr-CH, repoint them to de or fr respectively (or to de-CH if you want the Swiss German overlay). The narrow-BCP-47 resolver still falls back to the base locale at runtime, so nothing 500s — but text that previously came from de-AT.json / fr-CH.json will now come from de.json / fr.json.
5. Docs deployment (self-hosters who pointed external DNS at docs.tale.dev Mintlify)
If you previously had a CNAME pointing at Mintlify Cloud for your docs subdomain, switch it to your Caddy/reverse-proxy host. The bundled compose.docs.yml brings up the new tale-docs service on port 3002; Caddy is already configured for docs.tale.dev in Caddyfile. No action needed if you don't expose a public docs site.
Upgrade
tale upgrade
tale deploy
Contributors
@larryro, @AdeolaAdekoya, @Israeltheminer, @yannickmonney