Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> :toc: preamble :icons: font :sectnums:
image:Elixir
1.19+ image:OTP
27+
http-capability-gateway is a lightweight, policy-driven HTTP governance layer that enforces a declarative, auditable model of HTTP verb exposure in front of existing services.
This project introduces a minimal viable implementation of a new category: a capability gateway for HTTP. It does not replace nginx or Apache. Instead, it governs what they are allowed to do.
The gateway loads a Verb Governance Spec (DSL v1), validates it, compiles it into fast enforcement rules, and applies those rules to real HTTP traffic. Every decision is logged in structured form for audit and introspection.
This repository contains a real Elixir gateway implementation, but it should currently be treated as a narrow, in-progress API governance layer rather than a fully proven front door for an entire site.
The core policy pipeline exists: loader, validator, compiler, gateway, proxy, telemetry.
The main remaining gaps are security depth, end-to-end verification, and benchmark evidence.
Read
ROADMAP.adoc,TEST-NEEDS.md, andPROOFS_NEEDED.mdtogether when judging readiness.
It provides:
Declarative Verb Governance: Define allowed HTTP verbs globally and per-route
Stealth Mode: Return configurable status codes (404, 403, etc.) for unauthorized requests
Fast Policy Enforcement Architecture: ETS-backed lookups and compiled policy rules; benchmark evidence still needs to be formalized
Trust Level Integration: Current implementation is header-based, with mTLS-oriented direction documented but not the primary proved path yet
Comprehensive Logging: Structured JSON logs with telemetry metrics
Backend Proxy: Transparent proxying to backend services with header preservation
Wondering how this works? See EXPLAINME.adoc.
Modern systems expose HTTP methods inconsistently and often accidentally. DELETE, PUT, PATCH, OPTIONS, and even HEAD can leak capabilities or create attack surface when left unmanaged.
Traditional reverse proxies do not provide:
per-verb governance
narrative or provenance
reversible policy artefacts
trust-aware verb exposure
structured constraints
intentional stealthing or deception
http-capability-gateway introduces a principled, schema-driven approach to HTTP method governance without disrupting existing infrastructure.
The MVP focuses on the smallest coherent loop:
Load a Verb Governance Spec from disk
Validate it against a top-level schema
Compile it into fast, matchable rules
Enforce those rules on real HTTP traffic
Emit structured logs for every decision
No trust engine, no dynamic scoring, no control plane, no VeriSimDB integration. Those will grow around this core in later phases.
# Clone the repository
git clone https://github.com/hyperpolymath/http-capability-gateway.git
cd http-capability-gateway
# Install dependencies
mix deps.get
# Compile
mix compileCreate a policy file (
config/policy.yaml):dsl_version: "1"governance: global_verbs: - GET - POSTroutes: - path: "/api/admin"verbs: [GET] - path: "/api/users/[0-9]+"verbs: [GET, PUT, DELETE]stealth: enabled: truestatus_code: 404
Configure backend (
config/dev.exs):config:http_capability_gateway,policy_path: "examples/policy-dev.yaml",backend_url: "http://localhost:4000",port: 4000
Start the gateway:
mix run --no-halt # Or with interactive shell iex -S mix
Test requests:
# Allowed: GET on global route curl http://localhost:4000/api/public # Returns: proxied response from backend# Denied: DELETE not in global verbs curl -X DELETE http://localhost:4000/api/public # Returns: 404 (stealth mode)# Allowed: PUT on specific route curl -X PUT http://localhost:4000/api/users/123 # Returns: proxied response from backend
dsl_version: "1"# Required: policy format versiongovernance:
# Global verbs: allowed on all routes unless overriddenglobal_verbs:
- GET
- POST# Route-specific rules (optional)routes:
- path: "/api/admin"verbs: [GET] # Only GET allowed, overrides global
- path: "/api/users/[0-9]+"# Regex patterns supportedverbs: [GET, PUT, DELETE]stealth: # Optional stealth mode configurationenabled: truestatus_code: 404# Status code for denied requestsEarlier-format DSL (v0) uses a richer service/verbs/narrative
structure:
service:
name: ledger-apiversion: 1environment: devverbs:
GET: { exposure: public }POST: { exposure: authenticated }DELETE: { exposure: internal }routes:
- path: /accountsverbs:
DELETE:
exposure: internalnarrative: "Account deletion requires internal trust."stealth:
profiles:
limited:
unauthenticated: 405untrusted: 404narrative:
purpose: "Define safe verb exposure for ledger operations."GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
Literal paths:
/api/usersRegex patterns:
/api/users/[0-9]+(numeric user IDs)Wildcard patterns:
/api/posts/.+(any post path)
When a request is denied:
Stealth enabled: Returns configured status code (e.g., 404) with empty body
Stealth disabled: Returns 403 Forbidden
Valid stealth status codes: 200, 301, 302, 403, 404, 410,
500, 503
# Policy file path (default: config/policy.yaml)export POLICY_PATH=/path/to/policy.yaml
# Backend URL (required)export BACKEND_URL=http://backend:4000
# Gateway port (default: 4000)export PORT=4000
# Trust level header name (default: x-trust-level)export TRUST_LEVEL_HEADER=x-trust-levelconfig/config.exs (shared config):
importConfigconfig:http_capability_gateway,backend_url: System.get_env("BACKEND_URL","http://localhost:4000"),port: String.to_integer(System.get_env("PORT","4000")),trust_level_header: System.get_env("TRUST_LEVEL_HEADER","x-trust-level")config/dev.exs (development):
importConfigconfig:http_capability_gateway,policy_path: "examples/policy-dev.yaml",log_level: :debugconfig/prod.exs (production):
importConfigconfig:http_capability_gateway,policy_path: System.get_env("POLICY_PATH"),log_level: :infoExtract trust levels from mTLS certificates or HTTP headers:
# From header (current implementation)
curl -H "X-Trust-Level: high" http://localhost:4000/api/adminTrust levels can be used for:
Audit logging
Fine-grained access control (future feature)
Rate limiting (future feature)
Structured JSON logs with telemetry:
{
"timestamp": "2026-01-22T23:00:00.000Z",
"level": "info",
"message": "request_handled",
"request_id": "req-abc123",
"method": "GET",
"path": "/api/users/123",
"trust_level": "high",
"verb_allowed": true,
"stealth_triggered": false,
"response_status": 200,
"duration_ms": 45
}# Run all tests
mix test# Run specific test file
mix test test/policy_loader_test.exs
# Run property-based tests only
mix test --only property
# Run performance tests
mix test --only performance
# Run with coverage
mix test --coverCurrent tests cover the policy pipeline and some gateway behavior, but the repo still needs materially stronger evidence in the following areas:
security tests for token validation, request sanitization, and SSRF resistance
end-to-end request lifecycle tests
concurrency and reload testing
formal benchmark runs
Do not treat the current suite as sufficient proof for whole-site gateway deployment.
Policy File (DSL)
|
v
Policy Loader → Validator → Compiler
|
v
Gateway (Elixir)
|
v
HTTP Traffic → Enforcement → JSON Logs
See TOPOLOGY for a full visual architecture map and completion dashboard.
Policy lookups use a three-tier strategy that eliminates full-table scans:
Tier 1 — Exact Path (O(1)): Literal route patterns (no regex metacharacters) are stored with
{:exact,path,verb}ETS keys. A direct hash lookup resolves 90%+ of requests instantly.Tier 2 — Regex Routes (O(r)): Patterns containing regex metacharacters are tested only against other regex routes, not the entire table.
Tier 3 — Global Rules (O(1)): If no route matches, a final
{:global,verb}lookup catches global verb defaults.
For a 1000-route policy, this reduces from ~1000 regex evaluations per request to 1 hash lookup (typical case) or ~50 regex evaluations (edge case).
Performance-oriented design is present, but the benchmark story is not
yet strong enough to advertise hard numbers as release evidence. A
performance test file exists; benchmarking and concurrency validation
are still tracked as open work in ROADMAP.adoc and TEST-NEEDS.md.
All responses (including health/metrics endpoints) include OWASP-recommended security headers:
X-Content-Type-Options:nosniff— prevent MIME-type sniffingX-Frame-Options:DENY— prevent clickjackingReferrer-Policy:strict-origin-when-cross-origin— limit referrer leakageCache-Control:no-store,no-cache,must-revalidate— prevent caching of policy decisionsConnection:close— prevent connection reuse across trust boundaries
http-capability-gateway/
├── lib/
│ └── http_capability_gateway/
│ ├── application.ex # OTP application
│ ├── gateway.ex # HTTP gateway (Plug.Router)
│ ├── proxy.ex # Backend proxy (Req)
│ ├── policy_loader.ex # YAML policy loading
│ ├── policy_validator.ex # DSL v1 validation
│ ├── policy_compiler.ex # ETS compilation
│ ├── logging.ex # Structured logging
│ └── log_formatter.ex # JSON log formatter
├── test/ # Current automated tests
├── config/ # Elixir config files and default policy
├── examples/ # Example policies
└── docs/ # API documentation
Use ROADMAP.adoc as the current roadmap. The short version:
core policy pipeline exists
tests exist but are not yet strong enough for broad production claims
the recommended near-term role is route-scoped API prefiltering, not full-site gateway responsibility
benchmark, E2E, and security-hardening work remain open
Rate limits
Expanded stealth profiles
Hot policy reloads
Control plane
VeriSimDB integration for provenance
Distributed gateways
Agent introspection
Constraint engine
Formal proofs
Contributions welcome! Please:
Fork the repository
Create a feature branch
Add tests for new features
Ensure
mixtestpassesSubmit a pull request
This project treats governance as a first-class engineering concern. Policies are artefacts. Artefacts are reversible. Decisions have provenance. HTTP verbs become capabilities, not accidents.
This project is licensed under the Mozilla Public License, v. 2.0. See
the LICENSE file for details.
SPDX-License-Identifier: CC-BY-SA-4.0