Skip to content

Latest commit

 

History

2,188 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Permguard

ci licence

Permguard

Authorization policy, versioned like code and shipped like code.

Permguard keeps your policies in a content-addressed, Git-like ledger, distributes signed versions over a protocol built for it, and answers can this subject do this to this? — either from its own data plane, or from inside your process, at zero network cost.

       authors + CI                     ┌──────────────────┐
            │                           │  CONTROL  PLANE  │        the ledger
     permguard apply ────── NOTP ──────►│                  │   commits · trees · blobs
            │                           │  zones · ledgers │   signed heads · audit
            │                           └────────┬─────────┘
            │                                    │  NOTP: pull the objects,
            ▼                                    │  verify the signed head
    ┌───────────────┐                            │
    │   workspace   │                ┌───────────┴───────────┐
    │  manifest.yml │                ▼                       ▼
    │  cedar/ rego/ │       ┌─────────────────┐    ┌───────────────────────┐
    │  requests/    │       │   DATA  PLANE   │    │   YOUR  RUNTIME       │
    │  tests/       │       │  Permguard PDP  │    │  embed · sidecar      │
    └───────┬───────┘       │  HTTP · gRPC    │    │  same objects,        │
            │               └────────┬────────┘    │  same engines         │
    permguard test                   │             └───────────┬───────────┘
   (decide it offline,               └──────────┬──────────────┘
    no plane at all)                            │
                                       decisions + signed log
                                                │
                                                ▼
                                        back to the control plane,
                                        verifiable after the fact

Why this is not another policy server

Policy is a repository, not a blob. init, validate, plan, apply, pull, history — a workspace on disk, commits, trees and blobs, all content-addressed. A decision does not cite "the policy that was live"; it cites the exact commit and the identity of the policy that decided, and those identities survive a rename. You can check out the state a decision was made against, months later, and re-ask the question.

One question, many engines. A ledger holds partitions — Cedar here, Rego there — and a profile says which of them answer. admin consults the org chart and the guardrails; pipeline consults the rules for machines and loads nothing else. An explicit deny from any partition beats a permit from another, so "who is entitled" and "is it safe right now" can be written by different people, in different languages, and still compose.

Supported policy languages

Language Runtime Decides from Status
Cedar cedar (cedar-policy 4.x) the request stable
Rego rego (regorus) the request stable
Dogwood dogwood (amzn-dogwood-language) the request and a durable history experimental

A language is a build, not a deployment action: all three are compiled in. What a deployment chooses is whether a ledger naming a runtime will be served, and the experimental one is gated — see below.

Experimental: Dogwood, and deciding from what has happened

Cedar and Rego answer may this subject do this to this? from the request in front of them. Dogwood is Cedar plus history: a policy may ask what has happened recently — formerly, since, aggregations over a window — as well as what is being asked now.

@id("read_only_after_login")
permit (principal, action == Drupe::Action::"Read", resource)
when temporal {
  formerly within 1h Drupe::Action::"Login"::response{ input.user: context.input.user }
};

Answering that takes a durable history, so Dogwood is served through a second interface, permguard.api.pdp.temporal.v1alpha1: an occurrence is recorded into a hash-chained journal, made durable, observed, and only then decided against. Upstream supplies the language, its lowering to Cedar, validation and the per-request authorizer, and is explicit that its included interpreter is a reference implementation rather than a production one. Permguard supplies what a deployment needs around it — the policy lifecycle, multi-tenancy, a durable and bounded event history, provenance, replication, limits and operational safety.

It is experimental, and gated by two switches that must both be set:

experimental:
  dogwood:
    enabled: "true"        # this deployment accepts a contract that is not yet stable

dataPlane:
  events:
    enabled: "true"        # this plane keeps a durable event history
    producer_id: data-plane-eu-1

Saying one and not the other refuses to start, by name. v1alpha1 is honest: the wire and replication shapes may still change, and the switch is what makes accepting that a decision rather than a default that moved.

Working configurations ship as config.local-experimental.yml beside each server crate, and examples/dogwood-session-access is a whole ledger — schemas, policies, and a test that asserts the verdicts its README claims. The contract is documented under Temporal Interface.

Bring your own data plane. The objects are self-describing and the engines are a library. Run Permguard's data plane, or pull the ledger and evaluate in your own process — no PDP hop, no network on the decision path, the same manifest, the same engines, the same answer. permguard test is that path, in the CLI: it decides a workspace off disk, before anything is pushed anywhere.

NOTP, not "an API". Policy distribution is content-addressed transfer with negotiation — send what the other side is missing, and nothing else — over HTTP or gRPC, with a signed head at the end of it. A data plane refuses a ledger whose head it cannot verify, and refuses one whose engine range it is outside: an engine interpreting the same policies differently is a silent authorization bypass, not a compatibility note.

Decisions are evidence. Every decision can be recorded with the commit, the policy identity, the reason, and keyed commitments over what the caller supplied — proof of the inputs without keeping them. The log is hash-chained, shipped to the control plane, and verifiable afterwards by somebody who does not trust the plane that wrote it.

An interface Permguard owns. permguard.api.pdp.native.v1 is Permguard's native policy decision interface, designed for profile-based decisions evaluated across one or more heterogeneous policy partitions. It is not an implementation of, nor a compatibility claim for, anybody else's authorization API. The shape will look familiar — a subject, an action, a resource, a context in; a decision and a reason out — because that is the obvious shape for the question. What owning it buys is that the parts nobody else specifies are specified here, in crates/permguard-languages/src/request.rs, and can change when Permguard needs them to. No badge, and no promise somebody else's document still holds.

Install

brew install permguard/tap/cli
permguard --help
From this checkout instead
cargo install --path crates/permguard-cli --bin permguard --force
export PATH="$HOME/.cargo/bin:$PATH"

Needs Rust 1.97+, cargo, and task or make. Docker Compose for the observability lab, jq for the JSON examples, k6 only for load testing.

Ask a server what it hosts

Start with what the Server Host and its planes say about themselves, because it is the shape of everything below. Nothing is agreed out of band: a client is handed one Server Host URL and reads the rest off it — which planes are present, where to push policy, where to ask for a decision, which keys sign what, and what each interface offers.

All three endpoints are up after task run:all, which is the first command of the next section.

The Server Host — which planes this process hosts

curl -s http://127.0.0.1:5443/.well-known/server-configuration | jq
{
  "planes": {
    "control-plane": {
      "server_configuration": "http://127.0.0.1:6443/.well-known/server-configuration"
    },
    "data-plane": {
      "server_configuration": "http://127.0.0.1:7443/.well-known/server-configuration"
    }
  }
}

Port 5443 is the Server Host operations surface: process discovery, health, readiness, version, and metrics. The public plane APIs stay on their own ports. Every shipped Server Host configuration uses 5443. The assigned role port does not change between HTTP and HTTPS: the scheme describes transport security, while the port identifies the server role. HTTP and gRPC share that role port and, when TLS is enabled, the same TLS or mutual-TLS policy.

Server role Port
Server Host operations 5443
Control Plane 6443
Data Plane 7443
Trust Plane 8443

Containers and pods have isolated network namespaces. If standalone binaries must share one network namespace, give their Server Host listeners different IP addresses or use separate network namespaces; do not change the role's assigned port.

The control plane — where policy is published

curl -s http://127.0.0.1:6443/.well-known/server-configuration | jq
{
  "plane": "control-plane",
  "transports": {
    "http": true,
    "grpc": true
  },
  "jwks_uri": "http://127.0.0.1:6443/control-plane/keys",
  "notp": {
    "media_type": "application/vnd.permguard.notp.v1+cbor",
    "compression": "deflate",
    "ref_endpoint": "http://127.0.0.1:6443/v1/zones/{zone}/ledgers/{ledger}/refs/{ref}",
    "push_negotiation_endpoint": "http://127.0.0.1:6443/v1/zones/{zone}/ledgers/{ledger}/notp/push/negotiate",
    "push_commit_endpoint": "http://127.0.0.1:6443/v1/zones/{zone}/ledgers/{ledger}/notp/push/commit",
    "pull_negotiation_endpoint": "http://127.0.0.1:6443/v1/zones/{zone}/ledgers/{ledger}/notp/pull/negotiate",
    "object_upload_endpoint": "http://127.0.0.1:6443/v1/zones/{zone}/ledgers/{ledger}/notp/objects",
    "object_fetch_endpoint": "http://127.0.0.1:6443/v1/zones/{zone}/ledgers/{ledger}/notp/objects/fetch"
  },
  "zones_endpoint": "http://127.0.0.1:6443/v1/zones",
  "ledgers_endpoint": "http://127.0.0.1:6443/v1/zones/{zone}/ledgers"
}

That notp block is the whole distribution protocol, discovered rather than agreed in advance: the media type, the compression, and the endpoints for negotiating a push, uploading only the objects the other side is missing, committing a ref, and pulling back. jwks_uri is where the keys that sign a head statement are published — which is what makes a mirror able to refuse a ledger it cannot verify.

The data plane — where decisions are made

curl -s http://127.0.0.1:7443/.well-known/server-configuration | jq
{
  "plane": "data-plane",
  "jwks_uri": "http://127.0.0.1:7443/data-plane/keys",
  "interfaces": {
    "permguard.api.pdp.native.v1": {
      "configuration": "http://127.0.0.1:7443/.well-known/permguard-pdp-v1-configuration"
    }
  }
}

A plane says who it is, what it signs with, and which interfaces it exposes — each pointing at its own configuration. Follow the link and the interface describes itself:

curl -s http://127.0.0.1:7443/.well-known/permguard-pdp-v1-configuration | jq
{
  "interface": "permguard.api.pdp.native.v1",
  "pdp": "http://127.0.0.1:7443",
  "endpoints": {
    "evaluation": "http://127.0.0.1:7443/access/v1/evaluation",
    "evaluations": "http://127.0.0.1:7443/access/v1/evaluations"
  },
  "capabilities": [
    "urn:permguard:pdp:v1:store-in-payload",
    "urn:permguard:pdp:v1:profile-selection",
    "urn:permguard:pdp:v1:partition-inputs",
    "urn:permguard:pdp:v1:principal",
    "urn:permguard:pdp:v1:structured-reasons",
    "urn:permguard:pdp:v1:boxcarring"
  ],
  "store_scope": {
    "in": "payload",
    "zone": "required",
    "ledger": "required",
    "profile": "optional"
  }
}

interface says exactly what this is, in one field. endpoints are the routes actually mounted — they come from the same constants the router does, so the document cannot advertise a path the plane does not answer. store_scope states the thing a caller would otherwise have to read prose to learn: zone and ledger travel in the body, profile is optional.

capabilities are promises, so each one is implemented here, tested here, and answered identically over HTTP and gRPC — the store in the payload, profile selection, partition inputs, principal, the split reasons, boxcarring. Nothing is listed because it is planned, and nothing that is only half-true: a caller that configures itself from this document and is then refused by the same server has been lied to.

A generic client walks that chain: plane, then interface, then endpoints. Permguard's own CLI does not — it is a versioned client for permguard.api.pdp.native.v1 and links against the same constants the server mounts its routes from, so the two cannot drift and there is no round trip to be told a path it already knows. Both arrive at the same endpoints.

So discovery is three layers, and each answers a different question:

Document Question
/.well-known/server-configuration on the Server Host (:5443) which planes does this process host, and where
/.well-known/server-configuration on a plane (:6443 or :7443) who is this plane, what keys, which interfaces
/.well-known/permguard-pdp-v1-configuration what does permguard.api.pdp.native.v1 offer here

Below that sit two more layers, in the ledger rather than on the wire: a profile names which partitions answer a request, and each partition declares the input contract a request may hand it. The examples are about those.

Five minutes

1. A workspace is a directory

examples/basics/manifest.yml — what this ledger is, what it holds, and how it may be asked:

runtimes:
  cedar:
    language: { name: cedar, constraint: ">=4.0.0" }
    engine:   { name: permguard, constraint: ">=0.1.0 <0.2.0" }
  rego:
    language: { name: rego, constraint: ">=1.0.0" }
    engine:   { name: permguard, constraint: ">=0.1.0 <0.2.0" }

partitions:
  cedar:
    runtime: cedar
    schema: true                                     # and every policy type-checks against it
    input: { type: permguard.cedar.entities.v1, required: false }   # what a request may hand it
  rego:
    runtime: rego
    schema: false

profiles:
  default: { type: permguard.api.pdp.native.v1, partitions: [cedar, rego] }  # both answer
  gateway: { type: permguard.api.pdp.native.v1, partitions: [rego] }         # only the machine rules

Beside it, the policies. Cedar:

@alias("document-readers")
permit (
    principal in Group::"finance",
    action == Action::"read",
    resource
);

@alias("document-owners")
permit (
    principal,
    action == Action::"write",
    resource
) when { resource.owner == principal };

@alias is a handle for people. What a decision cites is the identity Permguard derives and keeps across renames — so the audit trail still names the same policy after somebody tidies the file.

And Rego, in the same ledger, answering under its own profile:

# METADATA
# custom:
#   alias: gateway-access
package gateway.access

import rego.v1

default allow := false

allow if {
    input.subject.properties.role == "admin"
    input.action.name in {"create", "update", "delete"}
}

2. Ask it, with no server anywhere

permguard -w examples/basics validate    # parses, type-checks against the schema, derives identities
permguard -w examples/basics test        # and does it decide what you meant?
  ok    a member of the finance group may read a document      [default] permit by document-readers, gateway-access
  ok    writing a document somebody else owns is refused       [default] deny, nothing permitted it
  ok    the gateway profile answers with Rego alone            [gateway] permit by gateway-access

  asked these sources, compiled here

5 case(s), 5 passed, 0 failed.

Same engines a data plane uses, same routing, same resolution — compiled from the working tree. This is the "bring your own data plane" path, and it is also your test suite.

Run it through the Taskfile instead
task cli -- -w examples/basics validate
task cli -- -w examples/basics test

3. Now publish it and ask a real PDP

task run:all                             # Server Host :5443, control plane :6443, data plane :7443

permguard zones create acme
permguard ledgers create main-ledger --zone acme

permguard -w examples/basics init basics --language cedar,rego   # the example ships the sources; this tracks them
permguard -w examples/basics remote add origin http://127.0.0.1:6443
permguard -w examples/basics checkout origin/acme/main-ledger
permguard -w examples/basics plan
permguard -w examples/basics apply -m "lab policies"

The data plane mirrors it, verifies the signed head, compiles each partition once, and serves:

permguard -w examples/basics check -f requests/permit.json
permguard -w examples/basics --data-endpoint grpc://127.0.0.1:7443 check -f requests/permit.json
permguard -w examples/basics test --remote   # the same cases, against the plane

Straight at the API — the endpoint the discovery document above named:

curl -s -X POST http://127.0.0.1:7443/access/v1/evaluation \
  -H 'content-type: application/json' -H 'x-request-id: lab-1' \
  -d "$(jq '. + {zone: "acme", ledger: "main-ledger"}' examples/basics/requests/permit.json)" | jq
Run it through the Taskfile instead
task run:all

task cli -- zones create acme
task cli -- ledgers create main-ledger --zone acme

task cli -- -w examples/basics init basics --language cedar,rego
task cli -- -w examples/basics remote add origin http://127.0.0.1:6443
task cli -- -w examples/basics checkout origin/acme/main-ledger
task cli -- -w examples/basics apply -m "lab policies"
task cli -- -w examples/basics check -f requests/permit.json

4. What a request looks like

{
  "subject":  { "type": "User", "id": "alice" },
  "action":   { "name": "read", "properties": { "risk": "high" } },
  "resource": { "type": "Document", "id": "budget-2026" },
  "context":  { "environment": "production" },

  "partition_inputs": {
    "cedar": {
      "type": "permguard.cedar.entities.v1",
      "data": [ { "uid": { "type": "Group", "id": "finance" } } ]
    }
  }
}

subject, action, resource and context reach every partition of the profile. partition_inputs reaches one, by the partition's own name — because an entity store is written in Cedar's shape, a Rego document in JSON, and two Cedar partitions with different schemas hold different worlds. The ledger decides what each partition accepts; the type in the request is checked against that and never obeyed, so a caller can never pick the parser for bytes it also supplies.

An action's properties reach Rego at input.action.properties. Cedar cannot carry attributes on an action at all, so Permguard projects them into context.action — and refuses a caller who writes that key. One request, two readings, nothing for a caller to keep in step.

5. Read the decisions back, and verify them

permguard decisions list --zone acme --ledger main-ledger
permguard decisions tail --zone acme --ledger main-ledger --follow
permguard decisions get  <decision-id> --zone acme --ledger main-ledger

# and check the chain and the signatures, with the plane's published keys
permguard decisions list --zone acme --ledger main-ledger --verify --keys data-plane-keys.json

The decision id a caller gets back is the one the record carries: an answer and its evidence are joined by an identifier, not by a timestamp and a guess.

Examples

Example Domain What it shows
examples/basics users, groups, documents the platform end to end — apply, mirror, decide, read the decisions back, verify them, and two workspaces pushing at each other
examples/release-pipeline software delivery a realistic set of controls — team ownership, machine identities, separation of duties, incident-only rollback — and the evidence they leave
examples/dogwood-session-access session access the temporal interface: a policy that decides on what has already happened, and the durable history it reads

The reasoning behind the last two, written for somebody who has never used Permguard, is in docs/use-cases/release-pipeline.md and docs/use-cases/session-access.md.

Copy one into a scratch directory and take it apart:

mkdir -p playground/basics && cd playground/basics
task cp-basics          # or: task cp-rspipe, task cp-dogwood

The Dogwood one needs a plane with the temporal interface on:

task run:experimental        # both planes in one process, event path enabled

Run Commands

Use task:

task build                         # build the workspace
task run:all                       # control + data plane in one process
task run:control                   # control plane only
task run:data                      # data plane only
task run-as-tls:all                # all-in-one with TLS
task run-as-mtls:all               # all-in-one with HTTP+gRPC mTLS on each plane role port

Use make:

make build
make run-all
make run-control
make run-data
make run-as-tls-all
make run-as-mtls-all

Use the installed CLI:

permguard --help                   # CLI help
permguard inspect                  # inspect local planes
permguard config show              # show CLI configuration
permguard completion zsh           # shell completions

Use Cargo directly for runtime binaries:

cargo run -p permguard-all-in-one --bin permguard-all-in-one -- crates/permguard-all-in-one/config.local.yml
cargo run -p permguard-control-plane --bin permguard-control-plane -- crates/permguard-control-plane/config.local.yml
cargo run -p permguard-data-plane --bin permguard-data-plane -- crates/permguard-data-plane/config.local.yml

Server commands accept a config file and optional overrides:

cargo run -p permguard-control-plane --bin permguard-control-plane -- \
  crates/permguard-control-plane/config.local.yml \
  --public-http-addr 127.0.0.1:6443 \
  --public-grpc-addr 127.0.0.1:6443 \
  --telemetry-addr 127.0.0.1:5443 \
  --log-level debug \
  --log-format terminal

Local configs:

crates/permguard-all-in-one/config.local.yml
crates/permguard-all-in-one/config.local-tls.yml
crates/permguard-all-in-one/config.local-mtls.yml
crates/permguard-control-plane/config.local.yml
crates/permguard-control-plane/config.local-tls.yml
crates/permguard-control-plane/config.local-mtls.yml
crates/permguard-data-plane/config.local.yml
crates/permguard-data-plane/config.local-tls.yml
crates/permguard-data-plane/config.local-mtls.yml

CLI Workflow

Create and manage remote state:

permguard zones create acme --endpoint http://127.0.0.1:6443
permguard zones list
permguard zones get acme
permguard ledgers create --zone acme main-ledger
permguard ledgers list --zone acme
permguard ledgers get --zone acme main-ledger

Author and publish policies:

mkdir my-policies
permguard -w my-policies init my-policies --language cedar
permguard -w my-policies remote add origin http://127.0.0.1:6443
permguard -w my-policies checkout origin/acme/main-ledger
permguard -w my-policies refresh
permguard -w my-policies validate
permguard -w my-policies plan
permguard -w my-policies apply -m "update policies"
permguard -w my-policies status
permguard -w my-policies history
permguard -w my-policies verify

Inspect local objects:

permguard -w my-policies objects list
permguard -w my-policies objects list --tracked
permguard -w my-policies objects prune --dry-run
permguard -w my-policies objects cat <digest> --human

Ask for authorization:

permguard -w my-policies check --subject User:alice --action read --resource Document:budget
permguard -w my-policies check -f request.json
cat request.json | permguard -w my-policies check -f -
permguard check -f request.json --zone acme --ledger main-ledger -o json

Read and verify decision records:

permguard decisions list --zone acme --ledger main-ledger
permguard decisions tail --zone acme --ledger main-ledger --follow
permguard decisions get <decision-id> --zone acme --ledger main-ledger
permguard decisions export --zone acme --ledger main-ledger -o json
permguard decisions list --zone acme --ledger main-ledger --verify --keys data-plane-keys.json

Inside an example

examples/release-pipeline/
|-- manifest.yml            three partitions, two pdp profiles, one input contract each
|-- admin-cedar/            the org chart — teams, ownership, roles (schema, type-checked)
|   `-- model.cedarschema
|-- admin-rego/             the guardrails — deny only, and a JSON Schema over their own input
|   `-- guardrails.regoschema
|-- pipeline-rego/          what CI, the signer and the controller may do
|-- requests/*.json         twenty-three decision requests, refusals included
`-- tests/release.yml       what this workspace claims its own policies decide

Two of the three partitions run Rego. A profile compiles the partitions it names and nothing else, so pipeline never loads the guardrails and admin never loads the rules for machines. Each partition declares what a request may hand it — the org chart is required, the guardrails' list is optional, and the pipeline rules accept nothing — and the suite asserts every way a caller can get that wrong.

Each example has its own README with the commands and the decisions they produce. lab/ at the repository root is something else: the configuration of the observability stack below.

Observability Lab

Start both planes plus Prometheus, Grafana and Loki:

task lab:up
# or
make lab-up

Start only observability for planes running on the host:

task lab:observability
# or
make lab-observability

Print the local addresses:

task lab:where
# or
make lab-where

Defaults:

Grafana     http://127.0.0.1:7590
Prometheus  http://127.0.0.1:7591
Loki        http://127.0.0.1:7592
Host        http://127.0.0.1:5443   (all-in-one or control)
Host        http://127.0.0.2:5443   (standalone data)
Control     http://127.0.0.1:6443
Data        http://127.0.0.1:7443

Follow logs and stop the lab:

task lab:logs
task lab:logs SERVICE=grafana
task lab:down
task lab:clean

Load Testing

Start the benchmark server:

task bench:server

Run k6 profiles:

task bench:peak
task bench:ladder
task bench:shed
task bench:grpc
task bench:tls
task bench:hold

Those measure the transport with nothing behind it. Two measure the product:

task bench:decide      # a stateless decision. Needs a data plane mirroring a ledger
task bench:temporal    # an occurrence: recorded, decided, and under overlap. Needs task run:experimental

Send k6 metrics to the observability lab:

task lab:observability
task bench:grafana

Workspace Layout

Crate Purpose
permguard-core Shared contracts: storage, secrets, signing keys, audit, services and server host types.
permguard-std Default implementations behind feature flags.
permguard-transport HTTP, gRPC, TLS, mutual TLS, revocation, reload and graceful shutdown.
permguard-telemetry Liveness, readiness and metrics.
permguard-server Server host, service registry, command dispatch and plane composition support.
permguard-languages Built-in Cedar and Rego integration.
permguard-objects Canonical policy objects, digests, manifests and signed head statements.
permguard-notp Negotiated Object Transfer Protocol messages and codecs.
permguard-control-client Client-side access to endpoints, trust material, catalogs, NOTP, decisions and mirrors.
permguard-control-plane Control-plane binary, policy object storage, inventory and decision-log ingestion.
permguard-data-plane Data-plane binary, mirror loop, authorization endpoint and decision recording.
permguard-all-in-one Local runtime that runs control and data planes in one process.
permguard-cli permguard, the command-line interface and authoring engine.

Development

task check                   # lint, structural checks and tests
task test                    # workspace tests
task test PKG=permguard-cli  # one crate
task lint                    # clippy with warnings denied
task coverage                # coverage gate

Structural checks:

task check:core-deps
task check:seams
task check:systems
task check:headers

Equivalent Make targets are available:

make check
make test
make lint
make coverage

See the organization-wide contribution rules and this repository's compatibility promises.

Documentation

Area Guide
Operations Verify released container images

License

Apache-2.0. See LICENSE.

About

Permguard is the authorization engine for both worlds: enforce policies on today's systems, enforce continuity on tomorrow's. One engine for governance, AI agents, and distributed execution.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

68 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages