Table of Contents
- Introduction
- Why this matters
- High-level approach
- What we built
- How to run policy tests locally
- Docker build integration
- Next steps
We’ve started a project to centralize authorization logic using Open Policy Agent (OPA) compiled to WebAssembly (WASM) and enforced locally inside our Resource Servers via a small HTTP middleware.
Disclaimer: This blog post is automatically generated from project documentation and technical proposals using AI assistance. The content represents our development journey and architectural decisions. Code examples are simplified illustrations and may not reflect the exact production implementation.
Introduction
When Claude, Caroline, and I looked across the codebase we saw repeated authorization checks implemented differently in each service. That made policy reviews time-consuming and audits error-prone. We wanted a single, testable source of truth for authorization, but also the low latency required by our real-time APIs.
OPA compiled to WASM gave us both: Rego for policy authoring and WASM for shipping a compact, efficient runtime inside each service process. Below we summarize what we chose, why, and how we built the initial prototype.
Why this matters
Centralizing policies in Rego improves reviewability and testing, but remote enforcement adds network latency and operational complexity. Compiling policies to WASM and evaluating them locally provides near-zero enforcement latency and removes a runtime dependency on a remote PDP. It also simplifies auditing: each artifact is versioned and produced by CI so we can trace decisions back to policy commits.
Architecture
At a high level the architecture looks like this:
graph LR
Client --> API[Resource Server]
API -->|evaluate| WASM[OPA WASM runtime]
API --> Metrics[Observability]
CI -->|build| Artifacts[Policy Bundles]
Artifacts -->|publish| Registry[Artifact Store]
API -->|fetch| Registry
The Resource Server loads a policy bundle at startup or via hot-reload, evaluates a compact input (http, user claims, resource), and receives a structured decision (allow, status, context). Denies are logged and metricized.
Implementation highlights
- Policy CI:
opa fmt,opa test,opa build -t wasmare run in CI. The resultingpolicy.bundle.tar.gzcontainsbundle.wasmand metadata and is published to our artifact registry. - Docker: The final Node.js image includes the selected policy bundle under
/app/policies/{name}/{version}. We prefer building policies in CI and copying artifacts into the runtime image to keep images lean. - Middleware: A small
opa-wasm-loaderexposesevaluate(input)returning the policy result. We wire this intopreHandler(Fastify) or a Gohttp.Handlerwrapper.
Example flow
- A client requests
GET /matches/123. - Middleware constructs
inputfrom request path, method, headers andrequest.userclaims. - The WASM evaluator runs the policy and returns
{ allow: true }or{ allow: false, status: { code: 403, message: 'forbidden' } }. - Middleware enforces the result and records metrics.
Running and testing policies locally
To author and validate policies locally:
# format and run unit tests
opa fmt -w ops/policies
opa test ./ops/policies -v
# build the WASM bundle
opa build -t wasm -e policy/main ./ops/policies -o ./ops/policies/bundles/policy.bundle.tar.gz
# quick smoke eval (optional)
echo '{"input":{"http":{"method":"GET"}}}' | opa eval --format=json -d ./ops/policies/bundles/policy.bundle.tar.gz 'data.policy.main.allow'
CI should run the same steps and refuse to publish artifacts unless tests pass and a smoke evaluation succeeds.
Key learnings so far
- Keep policy inputs small and deterministic — large inputs hurt performance and complicate testing.
- Run
opa testfrequently; policy regressions are often subtle and tests catch them early. - Enforce resource limits (time/memory) in the WASM runtime to protect services from expensive decisions.
Next steps
- Build the
opa-wasm-loaderhelper and publish a small example service in the repo. - Add a GitHub Actions workflow that runs
opa testand publishes the bundle to the artifact registry. - Pilot in staging in advisory mode and iterate on policies and input shapes.
If you want to try this locally, run the opa commands above and then start your service with a policy bundle in /app/policies.
Why this matters
Our codebase had authorization checks scattered across services. That made policies hard to review, test and evolve. By authoring policies in Rego, building them into WASM artifacts, and loading them directly inside the service process, we get:
- Reusable, testable policy logic (Rego) with unit tests.
- Low-latency, offline enforcement without a remote PDP call.
- Clear separation of policy from business code and standardized deployment.
High-level approach
- Author policies in
ops/policies/. - Run
opa testin CI; build the WASM bundle when tests pass. - Publish policy bundles as artifacts and copy them into service Docker images (multi-stage build) or mount them at runtime.
- Middleware in Node.js and Go evaluates policies per-request using a structured
inputand enforces allow/deny.
What we built (proposal)
The proposal covers:
- A lightweight
opa-wasm-loaderhelper for Node.js and a Go evaluator. - Middleware examples (Fastify and
net/http). - CI pipeline to format, test, build and publish WASM bundles.
- Docker integration: copy
policy.bundle.tar.gzinto final images in the multi-stage Node.js build. - Observability, hot-reload, and rollout guidance (advisory -> canary -> enforce).
How to run policy tests locally
Install OPA (https://openpolicyagent.org). Then from the repo root:
opa fmt -w ops/policies
opa test ./ops/policies -v
Build the WASM bundle:
opa build -t wasm -e policy/main ./ops/policies -o ./ops/policies/bundles/policy.bundle.tar.gz
Add this to your CI to fail fast on policy errors and publish validated bundles for deployment.
Next steps
- Implement the
opa-wasm-loaderhelpers and a small example service. - Add GitHub Actions job to run
opa testand build/publish bundles. - Start with an admin-policy pilot and run in advisory (log-only) mode in staging.
If you’re interested in helping with policy authoring or the Node.js loader, ping the Platform team — we’ll add contributor docs and examples soon.