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 compile-
Create a policy file (
config/policy.yaml):dsl_version: "1" governance: global_verbs: - GET - POST routes: - path: "/api/admin" verbs: [GET] - path: "/api/users/[0-9]+" verbs: [GET, PUT, DELETE] stealth: enabled: true status_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 version
governance:
# Global verbs: allowed on all routes unless overridden
global_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 supported
verbs: [GET, PUT, DELETE]
stealth: # Optional stealth mode configuration
enabled: true
status_code: 404 # Status code for denied requestsEarlier-format DSL (v0) uses a richer service/verbs/narrative structure:
service:
name: ledger-api
version: 1
environment: dev
verbs:
GET: { exposure: public }
POST: { exposure: authenticated }
DELETE: { exposure: internal }
routes:
- path: /accounts
verbs:
DELETE:
exposure: internal
narrative: "Account deletion requires internal trust."
stealth:
profiles:
limited:
unauthenticated: 405
untrusted: 404
narrative:
purpose: "Define safe verb exposure for ledger operations."-
Literal paths:
/api/users -
Regex patterns:
/api/users/[0-9]+(numeric user IDs) -
Wildcard patterns:
/api/posts/.+(any post path)
# 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):
import Config
config :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):
import Config
config :http_capability_gateway,
policy_path: "examples/policy-dev.yaml",
log_level: :debugconfig/prod.exs (production):
import Config
config :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 LogsSee TOPOLOGY.md 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 sniffing -
X-Frame-Options: DENY— prevent clickjacking -
Referrer-Policy: strict-origin-when-cross-origin— limit referrer leakage -
Cache-Control: no-store, no-cache, must-revalidate— prevent caching of policy decisions -
Connection: 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 documentationUse 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
Contributions welcome! Please:
-
Fork the repository
-
Create a feature branch
-
Add tests for new features
-
Ensure
mix testpasses -
Submit 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.
SPDX-License-Identifier: PMPL-1.0-or-later
See LICENSE.
Author: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
Repository: github.com/hyperpolymath/http-capability-gateway