Ferrum Anvil
Put your APIs to the test.
A local-first API client for desktop and the command line. Build requests, run repeatable tests and load tests, and troubleshoot Ferrum Edge with explanations grounded in what actually happened on the wire. No account, works offline.
- Status
- v0.1.1 previewPublished October 1, 2026; unsigned installers
- Ships as
- Desktop app + CLIWindows, macOS and Linux, plus the
anvilcommand - Built with
- Rust + Tauri 2React 19 and TypeScript renderer
- Ferrum Edge
- Catalogs for 0.9.8, 0.9.7 and 0.9.5Works with any API; no gateway required
SHA256SUMS. macOS installers are not Developer ID signed or notarized and Windows installers have no code-signing certificate. In-app updates use verified minisign signatures; this does not code-sign the installers. See preview downloads or Getting Started. Screenshots remain historical pre-release captures.
One Engine to Send, Test and Explain
One Rust engine prepares, sends, observes and diagnoses every request. The desktop app, the anvil command-line client, the collection runner and the load worker all use it.
Every protocol you need
HTTP/1.1, HTTP/2 and HTTP/3, WebSocket, gRPC, gRPC-Web, SSE, TCP/TLS and UDP/DTLS, with live interactive sessions where the protocol allows them.
Findings from evidence
Deterministic rules read DNS, connect, TLS and protocol evidence. Each finding states its confidence, the leg it is about, who can act, what it does not prove and what to try next.
Load tests
Arrival-rate, virtual-user and fixed-iteration workloads with a load unit per protocol, HDR latency percentiles, a timeline, run comparison and HTML, JSON or CSV reports.
Auth, TLS and mesh identity
API key, Basic, Bearer, JWT, OAuth 2.0 with PKCE, Ferrum HMAC, DPoP, mTLS, private CAs, SPIFFE identities and HBONE tunnels, with verification on by default.
Local first and encrypted
Offline, with no account and no cloud sync. Everything you create is encrypted on disk, and the profile locks on demand, when idle or when the system sleeps.
Import and share safely
Import OpenAPI, WSDL, Postman, Insomnia, cURL and HAR with a preview. Exports leave secrets out by default, and nothing imported ever runs on its own.
Three Things Anvil Is For
Requests you can repeat
Workspaces with nested folders, saved requests with immutable revisions, environments, variables and history.
- WebSocket (with optional permessage-deflate), gRPC, gRPC-Web and SSE, each also over HTTP/3
- Mesh and edge testing: HBONE tunnels, SPIFFE Workload API identities (X.509-SVID and JWT-SVID), SNI override, CONNECT-UDP (MASQUE) and PROXY protocol v1/v2
- Opt-in 0-RTT early data for replay-safe requests, with
425 Too Earlyhandled as RFC 8470 describes - Import OpenAPI 2.0–3.2, WSDL 1.1, Postman, Insomnia, cURL and HAR
- Collection runner with datasets, chained extraction and JUnit, HTML or JSON reports
Findings from evidence
Deterministic rules read typed evidence instead of rewording status codes.
- DNS, connect and TLS phases, dispatch state, HTTP/2 signals, body completeness, gRPC trailers, WebSocket close codes
- Each finding states its confidence, the leg it is about, who can act, what it does not prove, alternatives and next steps
- Transport, application, assertions and dispatch are reported separately
Load with the same requests
Open (arrival rate), closed (virtual users) and fixed-iteration workloads over saved requests, one protocol per plan.
- A load unit per protocol: HTTP requests, gRPC calls and streams, SSE streams, WebSocket sessions, TCP exchanges and UDP/DTLS exchanges
- Runs in a separate worker process with the same request preparation as Send
- Balanced counts, HDR latency percentiles, a timeline, generator health and run comparison
- Every run needs an explicit authorization acknowledgement
Real Results, Nothing Mocked
Screenshots from the native app, driven by the automated desktop test suite against a real Ferrum Edge 0.9.7 gateway and local test servers on macOS.
Every result kept separate
A GET through a local Ferrum Edge 0.9.7 gateway returns 200. The summary keeps transport, application, tests and dispatch as separate results, next to timing, size and protocol.
- Transport completed, application success
- Tests not run, dispatch sent
- Pretty-printed body, rendered as inert text
Confirmed, because Anvil saw it
Here the failure is on your own connection: the server's certificate chains to a throwaway test CA that the TLS profile does not trust. Anvil observed this directly, so it says Confirmed.
- Records that nothing was dispatched
- Scoped to your connection, owned by the caller
- Never offers disabling verification as the default fix
Counts that balance
A completed fixed-iteration run, with latency percentiles from merged HDR histograms and timeouts counted separately.
- Started, completed and failure counts that add up
- Status codes and redacted failure samples
- JSON, CSV, timeline CSV and HTML exports
Evidence From Every Leg
Anvil observes its own connection directly. Through a declared Ferrum Edge gateway it also reads the gateway's markers, and keeps claims about the gateway-to-backend leg at the confidence that evidence allows.
observed directly
from markers
The same bytes everywhere
A manual Send, a collection run and a load iteration share one request preparation, so what you test is what you send.
Findings name the leg
A finding says whether it is about your connection or the gateway-to-backend leg, and who can act on it: you or the gateway operator.
Gateway knowledge is opt-in
Catalog matching happens only for a destination you declare as a Ferrum gateway. Everything else is diagnosed from Anvil's own observations.
Works With Any API. Knows More About Ferrum Edge.
Works with any API
Nothing in Anvil requires Ferrum Edge. Requests, tests, load runs and the core diagnostics work against any server, because findings come from what Anvil observed on its own connection: name resolution, TCP, TLS, the bytes it wrote, protocol signals, status and body completeness.
Enhanced diagnostics with Ferrum Edge
Declare a destination as a Ferrum gateway integration profile, naming its release, and Anvil also matches responses against that release's source-audited compatibility catalog: 540 client-observable outcomes for 0.9.8, 538 for 0.9.7 and 528 for 0.9.5.
Supported versions: Ferrum Edge 0.9.8 (the default for new gateway profiles), 0.9.7 and 0.9.5. A profile that names another release gets no catalog: Anvil reports ferrum.catalog.unavailable, matches no outcomes, and reads the tokens only with the coarse meaning every audited release shares.
A Ferrum-looking header is not enough
Any server can send X-Gateway-Error. Without a declared gateway profile, Anvil reports such a header as an unverified marker and does not attribute the response to a gateway.
What a Diagnosis Can and Cannot Tell You
Every finding carries its own confidence. Anvil reports what the evidence supports, lists the alternatives it cannot rule out, and never guarantees a root cause.
| Confidence | Meaning |
|---|---|
| Confirmed | Directly observed, for example a refused TCP connection or an untrusted certificate on your own connection. |
| Likely | Strong but indirect or spoofable evidence. Every claim derived from a Ferrum Edge marker stops here. |
| Unknown | The evidence cannot separate the listed causes, so all of them are shown. |
| Conflicting | The evidence points in different directions. |
Gateway Error Tokens Stay Coarse
Ferrum Edge 0.9.7 and 0.9.5 expose seven public tokens; 0.9.8 adds request_timeout. Each one covers several internal causes, so Anvil explains the category and does not turn a token into a precise cause.
| Token | What Anvil says | What Anvil never claims from the token alone |
|---|---|---|
connection_failure | The gateway could not set up a connection to the configured backend (DNS, TCP, TLS, pool and similar). | That TLS failed, that DNS failed, or that your client certificate is wrong (the gateway uses its own identity). |
backend_timeout | The gateway's backend deadline elapsed. | That the backend received the request, or that it is slow rather than unreachable. |
request_timeout (0.9.8) | The route deadline expired before a backend held the current attempt. | That no earlier retry reached a backend, or which phase used the budget. |
backend_error | The backend path failed or the gateway refused locally. | That the backend returned this error. |
circuit_breaker_open | The gateway's breaker for this backend is open. | That the backend is down right now. |
overload | The gateway shed load, was draining, or hit its response-transform ceiling. | CPU pressure. |
config_stale | The data plane's configuration fence is stale. | Which configuration change is missing. |
concurrency_limit | A concurrency limit rejected the request. | The limit value or the cause of the load. |
Markers are capped at “likely”
On Ferrum Edge 0.9.7 and 0.9.5 a backend can inject X-Gateway-Error and X-Gateway-Upstream-Status on some paths, so marker-based claims stay at likely even for a declared gateway over verified TLS. Edge 0.9.8 strips backend marker copies, but the response can still traverse another proxy. Edge v0.9.9+ provides opt-in diagnostic references; Anvil v0.1.1 has no catalog or authenticated resolver for those releases, so its gateway findings remain capped at likely.
Absence proves nothing either
A missing marker does not prove a response came from the backend. A 403 alone never proves a WAF, because the WAF and bot-detection default bodies are byte-identical and backend 403s look the same.
How the diagnostics are checked
The tagged failure lab supports checksum-pinned Ferrum Edge 0.9.8 (default), 0.9.7 and 0.9.5 binaries on loopback. Every scenario runs with and without a trusted gateway profile, and lookalike backend responses must not be blamed on the gateway. Skipped scenarios are never counted as passes. The tagged lab defines 14 profiles, including MCP. Refer to the published release evidence for platform and run results; these pages do not certify a fresh lab run.
Offline, no account, locked when you leave
Anvil needs no sign-in, no Ferrum account and no cloud sync. What you create or observe is encrypted on disk with XChaCha20-Poly1305; only structural fields such as ids and timestamps stay in the clear.
- Start without a password: the data key goes to the OS keychain (macOS Keychain, Windows Credential Manager or a Secret Service such as GNOME Keyring or KWallet); where none exists, Anvil asks for a passphrase and never stores data unencrypted
- Or use a passphrase, with a recovery key shown once. Better on a shared computer
- Lock with a button, ⌘/Ctrl+L, an idle timeout or system sleep
- Enforced by the backend: locking drops the data key, clears cached OAuth tokens, Workload API identities, TLS session tickets and pooled connections, and stops running requests, sessions and load workers
Protocols and Authentication
Support is rated per use: sending and automation, interactive sessions, and load tests.
| Protocol | Send and automate | Interactive session | Load tests |
|---|---|---|---|
| HTTP/1.1, HTTP/2 (TLS or h2c) | Yes | Request/response | Yes: one HTTP request per unit |
| HTTP/3 (forced, or automatic fallback) | Yes; a fallback is recorded as its own attempt | Request/response | Yes: fallback attempts are counted separately, and their time is part of the request's latency |
| 0-RTT early data (opt-in, off by default) | Yes: QUIC 0-RTT for HTTP/3, and TLS 1.3 early data with the HTTP/1.1-only or HTTP/2-only policy. Only GET, HEAD and OPTIONS, plus PUT, DELETE or TRACE if you list them; a 425 Too Early gets at most one retry, after the handshake | Request/response | Refused |
| WebSocket (HTTP/1.1 Upgrade; HTTP/2 or HTTP/3 extended CONNECT) | Yes; over HTTP/3 it needs wss:// and never falls back to another bootstrap. Optional permessage-deflate (RFC 7692), off by default and chosen per request; the message size limit applies after decompression | Yes | Yes: one WebSocket session per unit, counted as opened, rejected or not opened, with messages and close codes; round trips only when the request expects replies |
| gRPC unary and server streaming (HTTP/2, TLS or h2c) | Yes | No, by design | Yes: unary calls counted by status code, where a missing status is never success; server streams counted per stream. Persistent mode reuses a pooled channel per virtual user |
| gRPC client streaming and bidirectional (HTTP/2, TLS or h2c) | Yes | Yes | Refused |
| gRPC over HTTP/3 (all four call modes) | Yes; forced HTTP/3 never uses TCP, and with fallback a separate HTTP/2 attempt is recorded only if QUIC failed before the call was sent | Client streaming and bidirectional, as over HTTP/2 | Unary and server streaming, as over HTTP/2; the other modes are refused |
| gRPC-Web, binary and text (HTTP/1.1, HTTP/2 or HTTP/3) | Unary and server streaming only, because the protocol has no client stream; a body without a trailer frame is reported incomplete, never success | No; there is no client stream to drive | Yes, as native gRPC |
| Server-sent events (HTTP/1.1, HTTP/2 or HTTP/3) | Yes | Receive only | Yes: one stream per unit, with events, time to first event and how it ended; automatic reconnect is refused |
| TCP, TCP+TLS | Yes | Yes | Yes: one TCP exchange per unit, with frames and bytes each way and whether the expected frames arrived |
| UDP, DTLS 1.2/1.3 | Yes | Yes | Yes: one UDP or DTLS exchange per unit. Datagrams sent and received are counted separately; silence is “no response observed”, never a delivery claim |
| UDP and DTLS through an HTTP/3 CONNECT-UDP proxy (MASQUE, RFC 9298) | Yes; DTLS runs end to end with the target inside the tunnel | Yes, as for UDP and DTLS | Refused |
| PROXY protocol: v1/v2 header on TCP, TCP+TLS and HTTP-family requests; v2 datagram envelope on UDP and DTLS | Yes. On HTTP/1.1, HTTP/2, WebSocket, gRPC, gRPC-Web and SSE over TCP, the header is sent once per new connection, and pooled connections are reused only by requests with the same header. Refused over HTTP/3 and through any proxy. The datagram envelope can be authenticated as Ferrum Edge defines it | Yes, as for the carried protocol | Yes, for load units over TCP: one header per new connection |
| Mesh client: HBONE tunnels (HTTP/2 CONNECT over mTLS) | Yes, carrying HTTP, WebSocket, gRPC, SSE and raw TCP, and UDP and DTLS as a Ferrum Mesh datagram tunnel; the endpoint can be verified by SPIFFE ID or trust domain (X.509-SVID). Tunnel-leg failures get their own hbone.* findings and never blame the destination | As for the carried protocol | Refused for UDP and DTLS, and for HTTP or gRPC in persistent mode; other load through HBONE has not been exercised |
permessage-deflate through Ferrum Edge
Ferrum Edge 0.9.7 and 0.9.5 strip the offer before the backend handshake, so through the gateway Anvil reports “offered, not negotiated” and the session runs uncompressed. Compressed sessions are proven against test servers and an independent implementation.
PROXY headers on HTTP requests
For listeners behind a PROXY-speaking balancer, such as HAProxy, nginx or Envoy. Ferrum Edge's HTTP listeners do not accept a PROXY header; Anvil reports the listener's answer as observed and lists a listener that does not expect the header as a possible cause.
0-RTT early data
Sent only for replay-safe methods, and session tickets live in memory only, cleared on lock. Ferrum Edge 0.9.7 and 0.9.5 accept early data only on their HTTP/3 listener, when it is enabled there; their HTTPS listener resumes sessions without it.
Known gaps
Not implemented: double HBONE, HBONE over QUIC, CONNECT-IP and WebTransport, none of which Ferrum Edge supports either; CONNECT-UDP over HTTP/2; PROXY headers over HTTP/3 or through a proxy; WebSocket extensions other than permessage-deflate; load actions that hold sessions open while sending messages at a rate; load runs through CONNECT-UDP or HBONE datagram tunnels.
Auth and TLS
Workload API identities are checked in the lab against Ferrum Edge's own Workload API, a development and test feature of the gateway, and against its jwks_auth plugin, on the supported 0.9.8, 0.9.7 and 0.9.5 releases. SPIRE itself is not in the lab.
| Authentication | Details |
|---|---|
| API key | Header or query parameter; query values are redacted in records. |
| Basic, Bearer | Applied to the final request on every send. |
| JWT signing | HS, RS and ES algorithms; time claims are regenerated per send. |
| OAuth 2.0 | Client credentials, refresh, and authorization code with PKCE in the system browser. Tokens are held in memory only and cleared on lock. |
| Ferrum HMAC v2 | Signed over the final bytes with a fresh nonce per send. Legacy v1 is an explicit opt-in. |
| DPoP | A new proof for every send. |
| WS-Security | UsernameToken and user-supplied SAML. |
| JWT-SVID | A SPIFFE JWT-SVID as a bearer token, fetched from a SPIFFE Workload API or read from a file or a variable. Checked locally before sending: format, algorithm, SPIFFE subject, audience, expiry and, optionally, the signature against the trust domain's bundle. A failed check stops the request unless you choose to send it anyway. The token itself is never recorded. |
| Multi-auth | Several mechanisms on one request. |
| TLS | Verification on by default; private CA roots; mTLS with PEM or PKCS#12 client certificates bound to hosts, or with an X.509-SVID fetched from a SPIFFE Workload API over a Unix socket (the profile's endpoint or SPIFFE_ENDPOINT_SOCKET); a verification bypass is scoped to a profile and warned about. A profile can verify the server by SPIFFE ID or trust domain (X.509-SVID) instead of its host name, and can override the SNI. |
Export, Import and Redaction
| Export mode | Contents | Secrets |
|---|---|---|
| Share safely (default) | One workspace | None. Literal secrets become {{placeholders}} listed in the bundle manifest. |
| Encrypted transfer | One workspace | Vault secrets, encrypted with a passphrase you share separately. |
| Full backup | Everything, including history and settings | Encrypted. Restores on a clean machine without the original keychain. |
Imports never run anything
Imports are previewed before they apply, checked for size, path traversal, symlinks, archive bombs and checksums, and applied in one transaction with a rollback checkpoint. They never send requests, run scripts or start load plans, and they switch risky settings back to safe values: TLS verification bypasses, plain-HTTP marker trust, cross-origin credential forwarding, the legacy HMAC opt-in, the 0-RTT early-data opt-in and sending a JWT-SVID that failed its local checks.
Redaction everywhere a secret could leak
Secrets are redacted by name and by exact value across execution records, history, reports, exports and support bundles, and credentials are stripped on cross-origin redirects. The effective-request preview shows Authorization redacted, and response content is rendered as inert text.
What Anvil Does Not Do Yet
- Unsigned preview. Downloadable v0.1.1 installers and CLI archives exist. macOS and Windows installers lack platform code signing; checksums and signed updater artifacts do not replace it.
- Published platforms. macOS x86_64 and ARM64; Windows x86_64; Linux x86_64. No Windows ARM64 or Linux ARM64 installer is published. Release evidence records native checks; minimum OS versions are not a published guarantee.
- Diagnoses are not verdicts. Findings report confirmed, likely, unknown or conflicting evidence with alternatives. They never guarantee a root cause.
- Coarse gateway tokens. The eight
X-Gateway-Errorvalues in the 0.9.8 catalog are explained as categories and are not turned into precise causes. Marker-based claims stay at likely. - Three gateway catalogs. Enhanced Ferrum diagnostics ship source-audited catalogs for Ferrum Edge 0.9.8, 0.9.7 and 0.9.5 only. The latest Edge v0.9.10 is outside this catalog set. A gateway profile that names any other release falls back to the release-independent token meanings (
ferrum.catalog.unavailable), with no outcome matching; those releases have not been validated. - Load tests have limits. Each protocol has its own load unit, but some combinations are refused before any traffic: client-streaming and bidirectional gRPC, gRPC with server reflection, SSE with automatic reconnect, UDP and DTLS through CONNECT-UDP or HBONE tunnels, requests with 0-RTT early data, HTTP or gRPC through HBONE in persistent mode, and plans that mix protocols. No load action holds sessions open while sending messages at a rate: a WebSocket unit is connect, scripted messages, close. Each run uses one worker process on one machine.
- Workload API coverage. SPIFFE Workload API identities are tested against Ferrum Edge's Workload API (a development and test feature of the gateway) and an independent test server, not against SPIRE. Windows named-pipe endpoints are implemented but checked only by CI.
- 0-RTT early data scope. HTTP requests only, over direct connections, and over TCP only with the HTTP/1.1-only or HTTP/2-only policy. Session tickets are kept in memory only, so a single
anvil sendprocess never has one. - Social sign-in is unavailable. Google, GitHub and Facebook sign-in stay unavailable until the providers are registered, so a linked identity and the fresh-login policy cannot be used yet. They are never needed to unlock Anvil.
- Updates are opt-in. Use Settings → Updates to check now or enable checks at launch. v0.1.1 updater artifacts use verified minisign signatures. Linux DEB/RPM installs require a manual package update.
- OAuth tokens do not persist. They live in memory only, so a browser sign-in (authorization code with PKCE) has to be repeated after a restart or a lock.
Tech Stack
All network, TLS, auth, storage and diagnostics code is Rust. The renderer talks to it only through typed IPC and never renders a response as HTML.
Quick Start
Use the v0.1.1 unsigned preview downloads, or build this exact release from source.
-
Clone the repository
bashgit clone https://github.com/ferrum-edge/ferrum-anvil cd ferrum-anvil git checkout --detach anvil-v0.1.1 -
Build and run the desktop app
bashcd apps/desktop npm ci npm run build # the desktop crate embeds dist/ at compile time npx tauri dev # run the desktop appOn first run, choose to start without a password (OS keychain) or protect the profile with a passphrase.
-
Or use the CLI
bashcargo install --path crates/anvil-cli anvil profile create me # passphrase via --passphrase-stdin or ANVIL_PASSPHRASE anvil workspace create Demo anvil add Demo "Health" --url https://example.com/health --folder Smoke anvil send Health --workspace Demo anvil run Demo --folder Smoke --junit report.xmlThe Getting Started guide covers the tests and the optional real-gateway failure lab.
Preview Guides
Getting Started →
Build the engine, CLI and desktop app from source, and optionally run the real-gateway lab.
Ferrum Diagnostics →
Confidence levels, gateway profiles, and what markers can and cannot prove.
Auth and TLS →
Auth types, OAuth sign-in, SPIFFE Workload API identities, verification on by default, and the scoped bypass.
Portable Data →
Workspace export and import, secrets left out by default, encrypted transfers and backups.
Load Tests →
Load units per protocol, the authorization acknowledgement, the worker process, and how to read a report.
Source on GitHub ↗
The ferrum-anvil repository, with its README and design documents.
Works With the Rest of the Stack
Licensing
Ferrum Anvil is dual-licensed: PolyForm Noncommercial 1.0.0 for personal, research, educational and nonprofit use, and a separate commercial license for commercial use, such as in a for-profit company's infrastructure, a paid product or service, a hosted service, or bundled with commercial software.