Skip to content

API-Sheriff

Status

Build & Quality

Java CI with Maven Integration Tests

Last Build License Maven Central

Quality Gate Status Lines of Code Coverage

Container Image

Container Image Release

Cosign Signed Trivy Gate

Benchmarks

Integration Performance Integration Trend

What is it?

A security-focused API Gateway with a lightweight approach, built with Quarkus.

Important

0.1.1 is an ALPHA release. The configuration surface and the APIs may change without a migration path. Per this project’s pre-1.0 rules, breaking changes are made freely: keys are renamed or removed, values change meaning, and no deprecation shim, compatibility alias or upgrade tooling is provided. A gateway.yaml that boots today may fail to boot on the next release, deliberately and loudly.

What alpha does not mean. It does not mean the code is untested or the security posture is provisional — the gateway is boot-validated fail-closed, covered by unit and containerised integration suites, and gated on a blocking quality gate. It means the surface is not frozen.

Intended use: evaluation, integration and feedback. Try it, build against it, and report what breaks. Do not deploy it where an unannounced breaking change between releases would be costly.

See Known Limitations for what is known to be missing or constrained at this cut.

Maven Coordinates

<dependency>
    <groupId>de.cuioss.sheriff.gateway</groupId>
    <artifactId>api-sheriff</artifactId>
</dependency>

Modules

API Sheriff

The deployable Quarkus application module — core library, CDI producers, REST endpoints, and native executable target.

Integration Tests

Integration test coordinator — Docker infrastructure, native container builds, and IT suites with HTTPS support.

Performance Benchmarking

Integration benchmarks using k6 HTTP load testing with containerized environments.

Deployment

Production Docker Image

Build the native executable and production Docker image:

# Build native executable
./mvnw clean install -Pnative -pl api-sheriff -am -DskipTests

# Build production Docker image
docker build -f api-sheriff/src/main/docker/Dockerfile.native -t api-sheriff:latest api-sheriff/

Run with TLS certificates mounted at runtime:

docker run -p 8443:8443 -p 127.0.0.1:9000:9000 \
  -v /path/to/certs:/certs:ro \
  -e QUARKUS_HTTP_SSL_CERTIFICATE_FILES=/certs/server.crt \
  -e QUARKUS_HTTP_SSL_CERTIFICATE_KEY_FILES=/certs/server.key \
  -e QUARKUS_MANAGEMENT_SSL_CERTIFICATE_FILES=/certs/server.crt \
  -e QUARKUS_MANAGEMENT_SSL_CERTIFICATE_KEY_FILES=/certs/server.key \
  api-sheriff:latest
  • Port 8443 (HTTPS) — the data-plane edge. Routes are deny-by-default: only the paths declared by the mounted gateway.yaml anchors and endpoints/*.yaml are served, and every other request is rejected with 404. The image ships no built-in application endpoints of its own

  • Port 9000 (HTTPS) — management endpoints (/q/health, /q/metrics). The management interface has exactly one port, so supplying QUARKUS_MANAGEMENT_SSL_CERTIFICATE_* above converts 9000 itself to HTTPS; there is no simultaneous plain-HTTP listener. It is additionally bound to 127.0.0.1 (loopback only) so the unauthenticated management interface is not exposed on all host interfaces, matching the security posture documented in doc/architecture.adoc

Known Limitations

Verified as still open at the 0.1.1 cut. Each is a deliberate boundary or a known gap, not a surprise waiting to be discovered.

A route with no forward block now forwards everything (behaviour change on upgrade)

The forward block used to be allow-list-only: a request header or query parameter crossed to the upstream only if the route named it, so a route declaring no block forwarded nothing. It is now a three-mode policy — headers_allow / query_allow is a positive-list, headers_deny / query_deny is a negative-list, and declaring neither means everything crosses. An existing route that omitted the block, or omitted one of its two dimensions, therefore starts forwarding client input it previously withheld.

The bound matters as much as the change. This alters what crosses within routes the operator had already exposed; it never alters which routes are reachable. Route matching is unchanged and remains deny-by-default — a request matching no route still reaches no upstream. Nor does it leak credentials: Cookie never crosses under any mode, an inbound Authorization is re-admitted only where a route declares a positive-list naming it, and the hop-by-hop, framing and provenance headers the gateway owns are withheld regardless. Note also that a declared-empty list is not the absent state: headers_allow: [] is a positive-list naming nothing, and keeps forwarding nothing.

Mitigation, one line: declare a positive-list (headers_allow / query_allow) on any route whose previous no-block posture you were relying on. See Architecture — Forward Policy and ADR-0036.

Upstream response headers are relayed except for gateway-owned names

Both directions carry a gateway-owned never-relay set — the connection-specific and framing names, plus, on the request side, the credentials and provenance headers the gateway owns. Beyond those sets the two directions differ: a route can narrow what crosses on the request side (headers_deny / headers_allow), while on the response side the gateway does not strip identifying headers an upstream emits — Server, X-Powered-By and their kin reach the client unchanged. There is no response-header allowlist and no configuration knob suppresses them. Terminate such fingerprinting at the upstream, or in front of the gateway, if it matters to you.

The server-mode session store is single-node and in-memory

InMemorySessionStore is the only implementation of the SessionStore seam, and session.store accepts exactly one value, memory. There is no distributed or persistent option. A server-mode BFF deployment is therefore single-node or sticky-session, and every session is lost on restart. Capacity is reclaimed at the ceiling rather than continuously: an expired session is removed when its own id is next looked up, and otherwise keeps its slot until a new login finds the store at its oidc.session.max_sessions bound, which sweeps the expired entries and admits the login if that freed a slot. No scheduler, timer thread or periodic sweep is involved. Size max_sessions against expected peak concurrency — the ceiling refuses new logins fail-closed only when that many sessions are genuinely still live. Cookie mode is the stateless alternative by design.

The cookie-mode BFF has had no systematic threat-model pass

The threat model covers the cookie-mode surface through individual catalogue entries (the AES-GCM sealing posture, the login-cookie replay window, per-instance refresh-token reuse detection), but no dedicated end-to-end review of the cookie variant has been performed. The entries that exist are sound; the coverage is not claimed to be exhaustive.

Integration-test coverage has recorded gaps

Two contributor documents enumerate them rather than leaving them implicit: Declared-Limit Assertion Coverage records which declared gateway limits have a boundary assertion and which do not, and Test-Corpus Integrity records test methods whose assertions cannot support the behaviour their names claim. Read both before treating a green suite as proof of a specific bound.

FAPI 2.0 is not conformant

Three mandatory client requirements are unmet — PAR, sender-constrained tokens, and a FAPI-permitted client authentication method are not wired, even though the underlying engine supports them. See FAPI 2.0 Conformance Status. No FAPI conformance may be claimed for this release.

Rate limiting is not implemented

The rate_limit field is reserved and accepted, but it is ignored and no 429 is ever produced.

About

An API-Gateway focused on Security and a leightweight approach. It incorporates a BFF Token handling as well, FAPI 2.0

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages