Authorization you can read, test, and trust

An open-source policy language and engine for the entire stack — from web apps and APIs to IoT and AI agents. Readable policies. Dedicated testing framework. Request-response and real-time streaming.

policy "freeze during peak hours"
deny
    subject.role == "engineer";
    action == "deploy";
    resource.environment == "production";
    // In production, use real business hours:
    // <time.localTimeIsBetween("09:00", "17:00", "Europe/Berlin")>;
    // For this demo, the window toggles every few seconds:
    <time.toggle(8000, 4000)>;
obligation {
    "type": "notify",
    "channel": "ops-alerts",
    "message": "Deployment blocked: peak hours"
}

TODO: Why SAPL

TODO: Streaming

Placeholder

TODO: Readable Policies

Placeholder

TODO: Policy Testing

Placeholder

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="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="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
policy "permit on emergency"
permit
    action == "read" & resource == "time";
    "status".<mqtt.messages> == "emergency";
Test
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  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  1ms
      PASS  not-applicable then permit as status changes  1ms
      PASS  alternating decisions 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

TODO: Scenarios

TODO: AI Security / Spring Security / Data-Level Security / IoT & MQTT

TODO: Scriptable authorization — sapl check as a shell-level authorization gate. Exit code 0 = PERMIT, non-zero = deny. Use in shell scripts, cron jobs, CI/CD pipelines, Makefiles, git hooks — anywhere a command-line decision is needed.

European open source

SAPL is developed at FTK in Dortmund, Germany, and funded through EU research programmes. It is deployed in production across European research and industry projects protecting critical infrastructure and sensitive personal data.

SAPL secures virtual power plant operations across European island energy systems, protecting grid control and distributed energy resource management.

Horizon 2020 — Grant Agreement No. 957852

SAPL protects sensitive clinical data in a multi-centre mental health study for young people across seven European countries.

Horizon Europe — Grant Agreement No. 101080923

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.

Trusted by research and industry

Schneider Electric Aix-Marseille University Cardiff University Brunel University London CSIC RDIUP FTK WIZ Research Heriot-Watt University University of Edinburgh
Funded by the European Union

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.