Lacuna + ForceField: working two-provider security middlebox example (+ findings on authorization/headers interaction)#145
Open
carlosdenner wants to merge 1 commit intoFlared:mainfrom
Conversation
Adds a working integration example placing the ForceField LLM security gateway behind Lacuna. Both providers are BYOK -- Lacuna sends the upstream provider key as Authorization: Bearer alongside the FF tenant key as x-api-key, ForceField strips its own auth and forwards the bearer untouched to api.openai.com / api.anthropic.com. Verified end-to-end against a live FF Cloud Run gateway: OpenAI benign returns a real completion with usage tokens; Anthropic with a fake key gets the expected 401 from api.anthropic.com (proves routing); FF blocks jailbreak prompts on both paths. Includes local docker-compose variant and a gcp-cloud-run/ variant pointing at a hosted FF (no Tailscale dependency). PEP-723 smoke_test.py with 4 cases. README documents the two-header auth pattern and notes a couple of Lacuna config gotchas (OpenAI handlers don't extract model from body; capabilities_header without header = deny_all).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A complete, end-to-end-tested example of placing ForceField (an LLM
security gateway) behind Lacuna so a single Lacuna deployment can broker
both OpenAI and Anthropic traffic with BYOK upstream keys
and FF detector / PII / output-moderation enforcement in the middle.
The example ships:
examples/forcefield/lacuna-config.json— both providers, two-headerauth pattern.
examples/forcefield/docker-compose.yml— runnable locally; binds hostport via
${LACUNA_HOST_PORT:-8080}so it doesn't collide with FF'sedge-proxy.
examples/forcefield/smoke_test.py— PEP-723 script with 4 cases:benign + jailbreak for each provider.
examples/forcefield/gcp-cloud-run/— variant pointing at a hosted FFon Cloud Run (no Tailscale dependency).
examples/forcefield/claude_code.settings.json— Claude Code wiredthrough Lacuna.
examples/forcefield/README.md+gcp-cloud-run/README.md— explainthe data-flow and the BYOK trust model.
Verified end-to-end
Run from a fresh checkout against a live FF Cloud Run gateway, broker key
issued via FF's
/v1/onboardtenant manager:"What is 2 plus 2?")200,"2 plus 2 equals 4.", real OpenAI usage tokens"ignore all previous")403 prompt_injectionfrom FF401 invalid x-api-keyfrom Anthropic — proves Lacuna -> FF -> api.anthropic.com routing and BYOK passthroughmsg_ff_block_*envelopeTwo findings worth flagging to maintainers
While building this I hit two Lacuna behaviors that aren't really wrong
but cost a few hours each. Calling them out so you can decide whether to
patch, document, or leave alone. Happy to file separate issues if you'd
prefer.
1.
headers+authorizationinteraction is the only way to do dual-key authLots of security middleboxes follow a "tenant key + upstream key" model
where the proxy sees one credential and the upstream sees a different
one. Lacuna's
Provider::build_requestsets exactly one auth header (theAuthorizationenum), then merges the staticheadersmap.The configuration that works is:
This is a perfectly fine way to express it — but it's not obvious from
the docs that
headerssurvives alongsideauthorization. A one-linenote in the provider config reference would save the next person an
afternoon. (Happy to PR that note too if you want.)
2. OpenAI handlers don't extract
model, somodels: ["gpt-*"]denies allIn the authorizer flow,
Provider::inspect_requestis implemented forthe Anthropic / Bedrock / Gemini handlers (they decode the body to read
model), but the OpenAI chat-completion and responses handlers don't.Result:
request.modelisNone, so any non-*glob incapability.modelsrejects every request to OpenAI.The example works around it with
"models": ["*"]for the OpenAIprovider and a comment in the README. A small
inspect_requestimpl onthe OpenAI handler would be a nice follow-up — I can take a stab if it's
something you'd accept.
3. (bonus, less impactful)
capabilities_headerdefaults to deny-all when absentSetting
capabilities_header: "Tailscale-App-Capability"with no headerpresent makes
Capabilities::deny_all()block 100% of requests. This iscorrect fail-closed behavior and totally fine inside a tailnet, but it
made the example unusable for self-hosters who don't run Tailscale, so
the example leaves it unset by default and the README explains when to
turn it back on.
Why this matters for Lacuna
Lacuna's positioning as "the small, opinionated reverse proxy in front
of every LLM call you make" pairs naturally with security gateways like
ForceField, Lakera Gateway, etc. Having a worked example in-tree that
shows the pattern (capability-filter first, security-detector second,
upstream third, BYOK end-to-end) lowers the lift for anyone composing
Lacuna with a downstream policy engine.
Out of scope
examples/addition.provider keys).
Reviewer questions
the OpenAI
inspect_requestpatch into separate PRs if that's easierto land.
examples/forcefield/follows the pattern of any existingthird-party examples — let me know if you'd prefer a different
subdirectory or a separate
cookbook/style.Thanks for Lacuna.