What's New in v0.4.9
🇧🇷 Versão em português · 📋 Full CHANGELOG · 📦 Download
An October 2026 release, large and on two fronts.
The first is data subject rights: export, email correction, anonymization and deletion stop being a promise and become a working path in the gateway, each closed tightly enough not to turn into an account-takeover door or an account enumeration oracle.
The second is the gateway as an MCP server over HTTP gaining task
delegation: an external orchestrator can now ask the Garra LLM a question and
request a full task, with its own identity, its own budget, and secret
redaction. Around that: a dependency advisory sweep, the desktop updater with a
DMG and latest.json, and a band of reliability fixes.
Data subject rights come off the page
Four data-subject-rights endpoints (GET /v1/me/export, PATCH /v1/me,
POST /v1/me/anonymize, DELETE /v1/me) now work end to end.
- Full export (
GET /v1/me/export). It used to return account data only; it now includes the subject's personal content across all of their groups — messages they wrote (full body), mentions received (snippet), uploaded files, memories and tasks — in a single transaction that re-points the tenant GUC per group, with fail-closed RLS and a subject-id filter on both ends. The file declares what was left out (exclusions) and what hit a ceiling (truncated_sections), because a silently incomplete export is a false compliance claim. - Email correction with no blind swap (
PATCH /v1/me).users.emailis the login key, so changing it on an access token alone would be an account-takeover primitive. Since the repo has no email-sending infrastructure, the request is recorded aspendingplus audit, and applying it is a manual operator step with out-of-band proof of possession. The endpoint also does not reveal whether the requested address already exists — answering that would become an account enumeration oracle. - Deletion with a grace window and a dedicated worker (
DELETE /v1/me). The same transaction that lays the tombstone and revokes sessions enqueues the request with a 30-day grace period. The newaccount_purge_workerdestroys the subject's content and identifiers (message, comment, memory, file plus versions and blob in the object store) and leaves theusersrow as a tombstone (status = 'purged') — it is not removed, because five FKs point at it and removing it would erase third-party context; another member's message is never deleted by one person's request.audit_eventssurvives de-identified: it is the only proof the deletion happened. With no object store wired, the request is NOT closed — marking itcompletedwith a live blob would lie in the audit.
The worker runs with dedicated credentials and is resumable. The garraia_purge
role (migration 034) has only EXECUTE on the four SECURITY DEFINER deletion
functions — no table privileges, not BYPASSRLS. The worker only starts if
GARRAIA_PURGE_DATABASE_URL is set; without it the log warns and the manual
procedure still applies. Progress is persisted on the request's own row, in the
same transaction as the deletes: a crash midway re-runs neither the database pass
nor closes the request with a live blob.
The MCP HTTP gateway gains task delegation
POST /mcp already read state and sent messages. Now an external orchestrator
can also ask the Garra LLM a question and request a full task. Everything is
off by default and carries its own budget, which never shares the sending
one.
garra_askover HTTP (#1612). It only appears withgateway.mcp_http.allow_ask: true, accepts only the project's default model whileask_allowed_modelsis empty, and hasask_budget_per_minute(default 10). Thegarra askcore was extracted into thegarraia-askcrate, shared by the CLI and the gateway.- Orchestrators with their own identity (#1613).
mcp_http.orchestrators[]declares anameand akey_env— the key value never goes into the config. Each orchestrator has its own policy (a subset of tools and destinations intersected with the global allowlist), per-call attributable audit, and secret redaction in both payload and error. An empty list keeps the prior behavior. garra_agentover HTTP (#1615). The orchestrator requests a full task — read and write files within the allowed roots, search the web, inspect git, and, only with a sandbox, run a command — and gets thegarra.agent.v1envelope with a summary of each tool used. It only appears withgateway.mcp_http.allow_agent: trueand withagentin the orchestrator's tool list. It hasagent_budget_per_minute(default 2) and a time capagent_max_seconds(default 300, max 600). Input and output pass through secret redaction, and no prompt, response or tool output goes to the log.- Operator-extensible redaction (#1635).
gateway.mcp_http.redact_patternsis opt-in: up to 32 regexes of up to 512 characters, added to the built-in secret patterns. Useful for masking an environment-specific token format in the payload and the error that cross the bridge.
The garra_agent schema now announces the operator ceiling: timeout_secs
ships with maximum and default derived from agent_max_seconds instead of
fixed values — before, the schema said a value was valid and the refusal came
later; two contract tests now exercise bash over this bridge end to end
(#1626).
Security: a dependency advisory sweep (#1630)
The October sweep closes the advisories open in the graph:
rustls0.23.45 → 0.23.46 (RUSTSEC-2026-0346 through 0349).quinn-proto0.11.16 → 0.11.18 (RUSTSEC-2026-0338 through 0342).wasmtime/wasmtime-wasi49.0.2 → 49.0.3 (RUSTSEC-2026-0352 through 0362), which includes a filesystem sandbox escape when renaming a symlink with a trailing slash. The MSRV floor stays 1.96.chacha200.10.0 → 0.10.2.serde_yamlreplaced by the maintained forkserde_norway0.9.42, same API, to drop the archivedunsafe-libyamlfrom the graph (RUSTSEC-2024-0449).
On the same supply-chain axis: each release asset now gets a provenance
attestation verifiable with gh attestation verify (#1606); the WhatsApp
bridge's music-metadata moves to 11.16.1, closing two Dependabot alerts; and
the eight rust/path-injection alerts in uploads.rs and session_workspace.rs
enter the CodeQL ledger as false positives, audited per entry — the path
originates from a typed Uuid or a hex SHA-256 digest, neither of which
expresses traversal.
Reliability and operation
- Uploads and recurrence workers scan again (#1611). Both acquired a Postgres
advisory lock on one pool connection and tried to release it on another — and
an advisory lock is per session, so the lock survived every tick and, from the
first one on, replicas were permanently blocked from scanning. Both now
pin one connection from acquire to unlock, as
account_purge_workeralready did. The regression test found a second independent defect: the recurrence scan died before scanning over abigintthe Postgres function signature did not match. - Per-provider LLM timeout (#1593).
llm.<name>.timeout_secsoverrides the global window for that provider — useful for a slow host without affecting the rest. And0now truly means "no timeout"; before, it became an already-expired sleep that failed every LLM call in milliseconds. - Telegram: a network failure stops being reported as "invalid token"
(#1594).
connect()treated anyGetMeerror as an invalid token — under Termux, an SSL/DNS failure told the operator to swap a token that was fine. The error is now classified: the API answering fails immediately; network/IO retries with backoff and points at the network diagnostic. - The
bashallowlist evaluates chaining segment by segment (#1592). The allowlist denied ANY command with|,&&,||or;— withgit *andgrep *allowed, agit status | grep changeddied. Separators are now split into segments (a quote-aware scan) and the list checks EACH one: it matches only when all match. Command substitution, redirection and a bare&still deny the whole command. garraia doctor mcpvalidates that the MCP server actually came up (#1595). The doctor checked installation but never the MCP plan: a failed startup went unnoticed. The new subcommand spawns each stdio server through the same code as the gateway boot, counts the handshake's tools, and shuts the children down; exit 0 when all came up, exit 2 when one did not, useful in CI.garra updateandgarra rollbackno longer exit 0 on failure (#1603). The message now goes to stderr with the full error chain and the process exits with 70, matchingconfig check,doctoranddesktop..envis read from the current directory only (#1604).dotenvywalked up the parent directories until it found a.env, and a file at the repo root injected config and credentials into every command run from a subfolder. It is now a plainFile::open: the parent's.envno longer applies.- An invalid
GARRAIA_CONFIG_DIRfalls back with a warning (#1620). A placeholder value from.env.exampleor an empty one now falls back to the default config_dir with a warning, instead of bringing down any command — includinggarra config check— with an opaqueEACCES.
Desktop: a macOS DMG and an updater with latest.json (#1568)
The desktop was not shippable as a packaged distribution: there was no macOS DMG,
the updater plugin was on with an empty pubkey, and no workflow published
latest.json — the update button never worked. v0.4.9 emits the .app.tar.gz
and the DMG, builds the DMG in a build-macos-desktop job, and now generates and
publishes a signed latest.json alongside the binaries, at the endpoint the
app's updater already queried. A PR mirror verifies the bundle on macOS before a
tag exists. The item that still blocks the distribution channel — EV/OV code
signing — stays external and tracked.
Quality and CI
- The gateway request-path denies
unwrap()/expect()(#1569, first batch). Ten modules that take external input gain#![deny(clippy::unwrap_used, clippy::expect_used)], with both allowed in tests. The production occurrences became a typed error or a compile-time-checked constant. - A Postgres fixture unblocks 10 integration tests (#1570). A new job brings
up a
postgres:16.8-alpinewith the env vars the gateway bootstrap requires, and the ten#[ignore]auth, integration and voice tests — which exited silently without Postgres — now run for real in CI. - Block dependency bumps and flaky tests.
tokio1.53.3,serde_json1.0.154,uuid1.28,opentelemetry0.33.1, Tauri 2.12.2 and plugins, Playwright 1.64, and the Dockerfile onrust:1.99-slim; plus two flaky CI tests fixed.
New in the CLI: garra uninstall
garra uninstall removes the CLI from the machine — binary, garra alias,
Termux wrappers and .old/.new backups. --purge includes config, data and
the legacy ~/.garraia; --all-binaries scans PATH. It refuses with exit 78
when the daemon belongs to a systemd unit, and with exit 64 in a pipe without
--yes.
Documents: cleanup and contribution
- Legal and pricing drafts leave the public repo (#1616). An exposure audit
found documents marked "DRAFT — DO NOT PUBLISH" and the internal financial
model on
main. The files stop being versioned (now covered by.gitignore); final reviewed versions publish on garraia.org. Git history and existing forks are unaffected. - Contact domain corrected to
garraia.org(#1574). Some paths pointed atgarraia.cloud, which does not resolve. The project's real domain isgarraia.org, and the security email issecurity@garraia.org. - End-user contribution guide (#1577). How to run the pre-flight before
opening an issue, which template to use, where to ask for help, and why a
vulnerability never goes in a public issue. Linked from
SUPPORT.md. channels.mdstops sayingwhatsapp_linkeddoes not exist inconfig.yml(#1605). The linked-device channel has a documented entry andconfig checkvalidation; the old text pointed at an absence that was no longer there.
How to update
garra update # CLI; keeps the previous version and supports `garra rollback`
Fresh installs go through the usual installer.
Known limits
- Email change (
PATCH /v1/me) and closing a deletion with blobs depend on operator steps: email sending and a wired object store are not set up by default. - Windows installers and desktop bundles are still unsigned (Gatekeeper on macOS, SmartScreen on Windows); the distribution channel's EV/OV code signing stays external.
garra_ask/garra_agentdelegation over MCP HTTP is off by default; nothing changes until the operator turns it on and declares an orchestrator.