Authorization you can read, test, and trust
Policies your team can read, a testing framework that proves they work, and decisions that update in real time when context changes. Spring, Django, NestJS, .NET, and more.
Authorization for AI agents and MCP
AI agents call tools, access resources, and act on behalf of users. Each of these operations needs authorization that goes beyond allow or deny. SAPL evaluates policies inside your MCP server or AI framework, with full access to the request context. Policies can allow a tool call while attaching obligations: redact sensitive fields from the response, log the access, require human approval above a threshold, or cap a parameter value.
Framework-native
Decorators and annotations run inside your FastMCP, Spring AI, or NestJS server. Authorization is part of the application, not an external proxy. No extra infrastructure to deploy or operate.
Beyond allow/deny
Every decision can carry obligations and advice. Filter tool responses, transform parameters, enforce audit trails, or trigger approval workflows. The constraint engine executes these automatically.
Per-tool, per-resource, per-prompt
Policies see the full MCP operation context: which tool, what parameters, which user, what secrets. Control tool visibility, gate resource access, and constrain prompt arguments individually.
AI scenarios with working code
RAG Pipeline
Document-level access control in retrieval-augmented generation. Filter and redact retrieved content before it reaches the LLM.
AI Tool Authorization
Per-tool authorization for Spring AI applications. Control which tools agents can call and transform tool responses via obligations.
Human-in-the-Loop
Policy-driven approval workflows for sensitive AI operations. The policy decides when human confirmation is needed, not the code.
MCP Server Authorization
Authorize MCP tool calls, resources, and prompts inside MCP servers. Decorators, constraint handlers, JWT/ABAC.
Decisions that stay current
Every authorization engine evaluates a request and returns a decision, and SAPL handles that case with zero overhead. The difference is what happens when context changes. Traditional engines have no way to revise the decision, so the application enforces a stale result until the next explicit check. With SAPL, the application subscribes to decisions and the PDP pushes updates whenever underlying attributes change. This is event-driven rather than polling, which reduces both latency and resource utilization. Toggle the threat level below and watch.
set "api activity access"
priority deny or abstain
for action == "monitorActivity"
policy "lockdown on critical"
deny
"https://siem.local/threat"..level == "critical";
policy "full access for investigation"
permit
"https://siem.local/threat"..level == "high";
policy "redacted by default"
permit
"https://siem.local/threat"..level == "low";
obligation {
"type": "filterJsonContent",
"actions": [
{"type":"blacken", "path":"$.user", "discloseLeft": 1},
{"type":"blacken", "path":"$.ip"}
]
}
@EnforceRecoverableIfDenied
@GetMapping(
value = "/api/activity",
produces = MediaType
.TEXT_EVENT_STREAM_VALUE)
public Flux<ApiEvent> stream() {
return activityService
.streamAll();
}
| Time | Request | Status | User | Source IP |
|---|
Use it with any stack
Standalone server
Run SAPL Node as a dedicated authorization service. Any language can query it via the HTTP API. REST for one-shot decisions, Server-Sent Events for streaming.
Framework SDKs
One annotation or decorator per endpoint. Idiomatic integration for Spring, NestJS, Django, Flask, FastAPI, Tornado, FastMCP, and .NET.
In-process on the JVM
Add the PDP as a dependency to your Java, Kotlin, or Scala project. Policies evaluate in your process. No network hop, no sidecar, no separate service to operate.
All integration modes support single and multi-subscriptions, streaming and one-shot decisions, and the full obligation/advice/resource transformation model.
// SpEL expressions reference method parameters with #name
@PreEnforce(
action = "'notifyParticipant'",
resource = "{'recipient': #recipient, 'message': #message}"
)
public String notifyParticipant(String recipient, String message) {
return notificationService.send(recipient, message);
}
# Lambda builds resource from route params and JWT secrets
@pre_enforce(
action="proxy.php?url=exportData",
resource=lambda ctx: {
"pilotId": ctx.params.get("pilot_id", ""),
"sequenceId": ctx.params.get("sequence_id", ""),
},
secrets=lambda ctx: {"jwt": ctx.request.state.token},
)
async def get_export_data(pilot_id: str, sequence_id: str):
return {"pilotId": pilot_id, "data": "export-payload"}
// Arrow function accesses return value after execution
@PostEnforce({
action: 'readRecord',
resource: (ctx) => ({
type: 'record',
data: ctx.returnValue,
}),
})
@Get('record/:id')
getRecord(@Param('id') id: string) {
return this.records.find(r => r.id === id);
}
// ISubscriptionCustomizer builds the subscription programmatically
[HttpGet("exportData/{pilotId}/{sequenceId}")]
[PreEnforce(Action = "exportData", Customizer = typeof(ExportCustomizer))]
public IActionResult GetExportData(string pilotId, string sequenceId)
{
return Ok(new { pilotId, sequenceId, data = "export-payload" });
}
# Lambda builds resource from route params, with JWT secrets
@pre_enforce(
action="proxy.php?url=exportData",
resource=lambda ctx: {
"pilotId": ctx.params.get("pilot_id", ""),
"sequenceId": ctx.params.get("sequence_id", ""),
},
secrets=lambda ctx: {"jwt": getattr(ctx.request, "sapl_token", None)},
)
async def get_export_data(request, pilot_id: str, sequence_id: str):
return JsonResponse({"pilotId": pilot_id, "data": "export-payload"})
Test policies like you test code
SAPL is the only authorization engine with a dedicated policy testing language. Mock external data sources, emit streaming attribute changes, and assert that decisions update correctly, all declaratively. Enforce coverage thresholds as quality gates in CI/CD pipelines and generate coverage reports.
policy "permit on emergency"
permit
action == "read" & resource == "time";
"status".<mqtt.messages> == "emergency";
scenario "decision changes when emergency status changes"
given
- attribute "statusMock" "status".<mqtt.messages> emits "emergency"
when "user" attempts "read" on "time"
expect permit
then
- attribute "statusMock" emits "ok"
expect not-applicable
then
- attribute "statusMock" emits "emergency"
expect permit;
> sapl test --dir policies --testdir tests --policy-hit-ratio 100 --condition-hit-ratio 100
permit_on_emergency.sapltest
Permit access only during emergency status
PASS permit when MQTT status is emergency 13ms
PASS not-applicable when MQTT status is ok 1ms
PASS not-applicable when MQTT status is any other value 1ms
PASS indeterminate when MQTT PIP returns error 1ms
Policy scope - only read action on time resource
PASS not-applicable for write action even during emergency 1ms
PASS not-applicable for delete action even during emergency 1ms
PASS not-applicable for read on different resource 1ms
Streaming - decision changes dynamically with MQTT status
PASS permit then not-applicable as status changes from emergency to ok 1ms
PASS not-applicable then permit as status changes from ok to emergency 1ms
PASS alternating permit and not-applicable with multiple status changes 1ms
Tests: 10 passed, 10 total
Time: 27ms
Coverage:
Policy Hit Ratio 100.00% >= 100.00% PASS
Condition Hit Ratio 100.00% >= 100.00% PASS
Scenarios
Step-by-step guides for common authorization patterns, each with working code and runnable demos.
Spring Security
Secure a Spring Boot application with attribute-based access control. Method-level enforcement, embedded PDP, reactive policies.
AI Security
RAG pipeline authorization, MCP tool access control, human-in-the-loop approval workflows for AI agent operations. Coming soon.
Policy Testing
Declarative policy tests with the SAPLTest DSL, coverage reporting, and CI/CD quality gates. Coming soon.
Ready to evaluate SAPL?
Professional support, consulting, and training available from FTK in Dortmund, Germany. We help you plan your authorization architecture, integrate SAPL into your stack, and train your team.
Opens your email client. No data is collected or processed by this website.
European open source
SAPL originated in European research and is built around transparency, privacy, and digital independence. It is deployed in production across European research and industry projects protecting critical infrastructure and sensitive personal data. Open source, self-hosted, no vendor lock-in.
SAPL secures virtual power plant operations across European island energy systems, protecting grid control and distributed energy resource management.
SAPL protects sensitive clinical data in a multi-centre mental health study for young people across seven European countries.
®
Open source, self-hosted. Apache 2.0 licensed. No vendor lock-in, no external dependencies, no data leaves your infrastructure. Run the PDP embedded in your application or as a standalone server.
SAPL has received funding from the European Union's Horizon 2020 research and innovation programme under Grant Agreement No. 957852 (VPP4Islands) and from the European Union's Horizon Europe programme under Grant Agreement No. 101080923 (SMILE). The views expressed are those of the authors and do not necessarily reflect those of the European Commission.














