Deploy and operate the response-resistance proxy
Vallum gives an application-authenticated browser a truthful response path while making protected raw API automation unreliable. This guide reflects the repository’s implemented CLI, SDKs, protocol, storage, and limitations.
One file with the full integration surface: admission broker, every framework adapter, route policy reference, and the constraints agents get wrong.
Quickstart
The repeatable local demonstration starts the proxy, demo origin, legitimate SDK frontend, PostgreSQL, Redis, and Prometheus. It requires Docker Compose, a currently patched Go 1.25+ toolchain, Node 22+, curl, jq, and npm.
git clone https://github.com/LiteEagle262/vallum.git
cd vallum
docker compose -f deploy/compose.yaml up --build -d
./scripts/demo.shOpen http://127.0.0.1:8088 and sign in with demo / vallum-local-demo. The demo proves three views: direct origin truth on :9090, a raw Vallum response containing no protected truth, and exact reconstruction through the authenticated browser SDK.
Use the isolated topology, unique secrets, TLS, secure cookies, private management/storage networks, and an authenticated origin hop before evaluating a shared deployment. ./scripts/evaluate-isolated.sh runs that harder configuration and writes a JSON summary.
SDK packages
One framework-neutral browser core plus first-party adapters. The adapters own lifecycle and dependency injection only — every one speaks the identical wire protocol, keeps initialization in the browser, exposes explicit loading/error/retry state, and leaves global fetch untouched.
Choose one frontend recipe. Every adapter installs the core client automatically; the explicit client name below makes the complete runtime pair auditable. Add the admission package in the trusted backend, except when the Next.js server entry already provides that wrapper.
npm install @liteeagle226/client @liteeagle226/admission
npm install @liteeagle226/browser @liteeagle226/admission
npm install @liteeagle226/client @liteeagle226/react react @liteeagle226/admission
npm install @liteeagle226/client @liteeagle226/vue vue @liteeagle226/admission
npm install @liteeagle226/client @liteeagle226/svelte svelte @liteeagle226/admission
npm install @liteeagle226/client @liteeagle226/angular @angular/core @liteeagle226/admission
npm install @liteeagle226/client @liteeagle226/react @liteeagle226/admission @liteeagle226/nextjs next react
npm install @liteeagle226/render # optional; add to any frontend recipeAll nine packages are Apache-2.0, typed, SSR import-safe, independently publishable, and checked as real npm tarballs by npm run sdk:verify. Astro, Solid, Qwik, Lit, Alpine, Preact, and custom elements use @liteeagle226/client directly.
Build the admission broker first
The page load grants nothing, and the browser bundle cannot hold the Ed25519 signing key. An authenticated application backend must expose POST /.well-known/vallum/admission on the same public origin the page is served from. It authenticates its ordinary session, enforces same-origin policy and an issuance budget, derives subject and scopes from server-side authorization state, and signs a short-lived, one-time grant.
import { admissionConfiguration, createVallumRouteHandler } from "@liteeagle226/nextjs/server";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export const POST = createVallumRouteHandler({
configuration: () => admissionConfiguration(),
async authenticate(request) {
const session = await applicationSessions.read(request);
// Never read the subject or scopes from the request body.
return session ? { subject: session.userId, scopes: session.scopes } : null;
},
async rateLimit(_request, principal) {
return admissionBudgets.consume(principal.subject, 20, "1m");
},
});@liteeagle226/admission exposes the same handler for any Node framework that can hand it a standard Request. Both callbacks are required: there is no allow-all default. Generate the keypair with npm run keys:admission; the backend holds VALLUM_ADMISSION_PRIVATE_KEY and Vallum receives only VALLUM_ADMISSION_PUBLIC_KEY.
The backend’s environment variables and the proxy’s session.admission block are verified against each other. A mismatch, an expired grant, or a reused grant is rejected — grants are atomically consumed once.
Browser client and request proof
The SDK creates non-extractable Web Crypto keys, exchanges the application grant for wrapped AES-256-GCM material, and signs every protected request over method, canonical URI, body hash, timestamp, JTI, and server nonce. Redis atomically rejects proof replay before authorization, quota, or any origin call.
import { createVallumClient } from "@liteeagle226/client";
// The user must already hold the application's ordinary authenticated session.
const client = await createVallumClient({ endpoint: location.origin });
const response = await client.fetch("/api/protected");
const data = await response.json(); // the exact origin object
client.destroy(); // release session and key references with the owning UI scopeApplication code never selects a wire layout, identifies metadata aliases, or decodes a field. The SDK strips every false-view and prompt-carrier field, passes unprotected responses through unchanged, renews expired material and retries a safe request once, and fails closed on malformed, expired, replayed, or tampered protected data. Pass client.fetch or client.wrapFetch(existingFetch) into your API layer rather than patching global fetch.
Deployment topology
Keep the data plane independent from the product web app. Public protected paths terminate at an edge or load balancer and route directly to the Go proxy. The Next.js application provides the product UI and same-origin admission broker; it must not proxy all application traffic.
- Expose only the public proxy listener.
- Keep the management listener, Redis, PostgreSQL, and origin private.
- Authenticate the Vallum-to-origin hop with the configured HMAC header. The bundled verifier’s replay cache is process-local, so a replicated origin needs one shared atomic replay store.
- Route
/.well-known/vallum/admissionto the authenticated application backend on the same public origin used by the SDK.
Validate configuration before startup
The CLI implements run, validate, print-config, benchmark, and version. Configuration is YAML and every secret is an environment reference. Route, carrier, and synthetic-world policy is fully checked before the proxy starts — an invalid policy refuses to load rather than degrading silently, so run validate in CI.
go run ./cmd/vallum validate --config config/vallum.yaml
go run ./cmd/vallum print-config --config config/vallum.yaml
go run ./cmd/vallum run --config config/vallum.yamlversion: 1
application:
id: app_production
name: Protected API
environment: production
tenant_id_env: VALLUM_TENANT_ID
proxy:
listen_address: ":8080" # the only public listener
origin_url: "https://origin.internal"
origin_auth_header: "X-Vallum-Origin-Authorization"
origin_auth_secret_env: VALLUM_ORIGIN_AUTH_SECRET
max_transform_body_bytes: 1048576
management:
enabled: true
listen_address: ":9091" # private network only
session:
cookie_secure: true
cookie_secret_env: VALLUM_COOKIE_SECRET
quarantine_on_honeytoken: true
admission:
required: true
issuer: customer-frontend
audience: payments:production
max_grant_ttl: "30s"
public_keys:
- key_id: frontend-2026-08
ed25519_public_key_env: VALLUM_ADMISSION_PUBLIC_KEYRoute policies
Routes are evaluated in order, in two phases. Before the origin call Vallum selects the first path/method match and enforces authorization and quota from it, because the response content type is not known yet. After the origin responds it may select a later content-type variant — but a later variant can never weaken a fail_closed or fail_static route, nor add authorization the request-time route did not already impose.
Encrypts the canonical JSON for reconstruction by the authorized SDK.
Adds a session-stable false representation while preserving configured types and shapes.
Adds advisory prompt-injection carriers outside the canonical application object.
Composes encoding, scrambling, and optional injection under one route policy.
Authorization evaluates only grant-bound, proof-verified state — require_proof, require_subject, and required_scopes. Raw identity, role, and scope headers are never evidence. Quotas are atomic fixed-window Redis budgets across session, proof-bound identity, and mutation dimensions; an unavailable quota store fails into containment rather than into an origin call.
routes:
- name: internal-api
match: {path: "/api/internal/**", methods: [GET, POST]}
mode: hybrid
authorization:
require_proof: true
require_subject: true
required_scopes: ["route:internal-api"]
quota:
session: {limit: 120, window: 1m}
identity: {limit: 240, window: 1m}
mutation: {limit: 20, window: 1m}
data_minimization:
remove_json_pointers: ["/diagnostics/private_note"]
stealth: true
failure:
behavior: fail_closedFailure behavior is explicit: fail_open returns an untouched supported origin response when transformation cannot complete and therefore carries a deliberate disclosure consequence; fail_closed discards it; fail_static returns configured static content. V1 transforms bounded, UTF-8, non-streaming JSON over HTTP(S) only — route SSE, gRPC, and protocol upgrades around Vallum.
Containment, disclosure, and deception
Authorization failure, exhausted quotas, quarantine, and honeytoken events resolve locally without an origin call. The synthetic result uses a bounded, coherent session world instead of leaking whether an origin object or privileged route exists. The service_control_v1 profile generates a workspace, services, permissions, an audit record, one job, and one false credential that stay coherent for a session across a fixed transition vocabulary.
- Data minimization removes configured RFC 6901 pointers before encryption and scrambling, so removed values are absent from both representations.
- Deferred disclosure omits selected values from the response entirely — the wire body, the false view, and the decrypted truth alike — and redeems them later through a proof-bound, budgeted endpoint. It is the one layer whose effectiveness does not depend on an attacker failing to decode something.
- Render-only disclosure delivers selected values as pixels rather than DOM text. It is a one-shot reference you mount, an accessibility tradeoff, and targeted friction — not cryptographic secrecy.
- False affordances present fabricated routes, parameters, and privileged fields as ordinary API data with no imperative, so no normalization step removes them. Every actionable trigger is registered as a canary.
- Injection carriers are advisory and reversible. Never attach an authorization decision to a model obeying injected text.
Detection and response normalization
Session-scoped behavioral detection scores automated probing — enumeration, sequential-ID walking, method fuzzing, and high error rates — into the same risk pipeline as rule signals such as honeytoken use and canary-route access. A hostile session is force-contained; a flagged session pays an added per-response delay. No single request has to be individually blocked.
On routes that do reach the origin, response normalization strips fingerprint headers, cloaks origin error bodies, collapses selected statuses, and adds a timing floor with jitter. That removes the differential oracles — errors, headers, status, latency — an autonomous agent reads to confirm a hypothesis. It complements an inbound WAF, which inspects requests.
Storage, telemetry, and the console
Redis holds short-lived sessions, proof replay state, quotas, hashed honeytokens, synthetic worlds, and sealed deferred values until redemption or expiry. PostgreSQL stores sanitized application metadata, configuration snapshots, sessions, events, domains, and audit records.
The operations console uses a tenant-scoped API on the private management listener. Telemetry reads and validated policy writes are authenticated with a server-held console token; customer browsers never receive that token or connect to the data plane database. The web service has separate, least-privilege storage for product workflows such as support and legal documents. The management API requires both console_api.enabled and a resolved token; without either it is not mounted at all, and it is never exposed on the public listener.
management:
console_api:
enabled: true
token_env: VALLUM_CONSOLE_TOKEN
max_window: "168h"
# Unset disables console-driven domain registration entirely.
domain_cname_suffix: "edge.vallum.dev"Vallum does not persist plaintext request or response bodies, cookies, credentials, decoded payloads, or real scrambling source values. Deferred disclosure is the explicit origin-data exception: authenticated ciphertext exists briefly in Redis.
Security boundaries
Vallum is a response-resistance and origin-admission layer. It does not classify a caller as human or AI, and it is not a WAF, secure enclave, endpoint agent, sandbox, bot detector, or replacement for origin isolation.
- An agent controlling an authenticated browser can instrument the SDK and inspect reconstructed data. Non-extractable keys are not a hardware boundary.
- Blind and out-of-band attacks may not depend on response understanding.
- Prompt-injection carriers are advisory and reversible;
@liteeagle226/renderis reversible visual obfuscation, not a security control. - Security properties come from admission, proof, authorization, minimization, containment, deferred disclosure, isolation, and storage boundaries.
- Production readiness requires conventional controls around identity, secrets, network segmentation, patching, monitoring, and incident response.