Repository navigation
Releases
sentrykit 0.1.1
The first stable release, and the whole library.
sentrykit is a self-hosted, framework-agnostic authorization layer for Python, built on
AWS Cedar. Clip one @protect decorator onto any callable — a
web route, a plain function, an agent tool — and it decides, per call, whether the principal may
invoke it, which arguments they may pass, and which defaults to inject when they pass
none, then writes a masked, hash-chained record of what it decided. The caller proposes; the
policy decides, and what it decides includes the arguments, not only the call.
0.1.0rc1 published this same library, to prove the release chain end to end; nothing under
src/ changed between it and this release. 0.1.0 is skipped rather than released — a wheel
under that filename was built on a developer machine and uploaded to the test index while the
chain was being proved, and since the chain verifies that an index serves the exact artifact the
run just built, a number whose test-index copy came from somewhere else can never pass it.
Version numbers below 0.1.0rc1 belong to the development history and were never published.
Added
@protect, and one pipeline behind it. The decorator guards any sync or async callable and
runs gate, filter the proposed arguments, inject the defaults policy supplies, validate,
invoke, report. One module importscedarpy, parsing the policy set, the entity graph and an
optional schema at wiring time and reusing those handles per decision. The core imports no web
framework and no agent SDK, so the decision path is identical wherever it runs.- Ambient context, fail-closed.
establish()binds the principal, the channel and the facts
a policy may read —now,break_glass,on_behalf_of,justification;bind_context()
carries the scope across a thread-pool offload. With no principal established the call is
refused rather than run unguarded, so forgetting to wire a boundary denies instead of opening. - Per-(principal, action) argument constraints. Allow-sets, deny-sets, patterns, numeric
bounds,requiredand a per-principaldefault, resolved for the principal and the action and
decided one argument at a time. A deny-set is matched against what a value contains as well
as against the value itself, so wrapping a forbidden value in a list does not get it past. - Narrow-and-continue instead of refuse. A proposed value the principal may not have is
rejected and their own default fills the gap, so the call proceeds. It is reject-and-refill
rather than a clamp: the bound a value breached is never substituted for it. With no default
the argument is dropped, and if nothing the caller supplied survives the call is refused with
NoValidArguments— narrowing refines a proposal, it does not invent one. - Policy-supplied defaults, checked like everything else. A default fills an argument the
caller omitted and one that was just dropped, and is itself checked against the constraint
governing it and withheld if it fails, so omitting an argument is not a way around its rule. A
constraint nothing could satisfy — an uncompilable pattern, a minimum above a maximum, an
allow-set member the other rules reject, a default the constraint would refuse — is refused at
the grant write rather than found by whoever calls next. - A governed parameter cannot quietly take its value from the function definition. Where a
parameter has no surviving value, Python would fill the slot from the callable's own signature,
which no constraint ever saw. One that satisfies its constraint is named in
signature_defaults; one that does not refuses the call withSignatureDefaultRejected, whose
declaredflag says whether the rule was reviewed policy or the principal's own grant. - Argument ceilings, declared in reviewed policy text. A
@constraintsannotation on a Cedar
permitcarries{argument: {rule: value}}and says what anybody may pass for that action.
Ceilings and grants intersect and can only narrow — allow-sets intersect, deny-sets union,
bounds move inward,requiredis sticky, two patterns must both match — and a ceiling may not
set adefault, because per-principal values stay in the store. Declarations are checked at
load, since Cedar does not type-check annotation values, and a policy set declaring any ceiling
refuses an engine failure rather than proceeding unbounded. A ceiling is the one declaration a
policy can make that reaches narrow-and-continue rather than the gate. - An envelope that reports the outcome, not only the exceptions. Every decision reports what
was processed, rejected, defaulted and withheld, trimmed byverbosity, plusnarrowed,
narrowed_argumentsandguidance— a narrowed call succeeds, on values the principal is
entitled to, and reporting only its exceptions describes that success in the vocabulary of a
failure. Those three name no value and no policy id, so a caller who may not be told what was
narrowed is still told that the result is good. It arrives as the_authzkey on a mapping
return, vialast_envelope()for any other return type, and out of band per boundary:
x-sentrykit-narrowedresponse headers, MCP_metakeys with an appended text block, and a
Pydantic-AIToolReturnthat displaces no data. - A refusal says which kind it is:
AuthzDeniedfor a policy denial,NoContextfor no
principal, andNoValidArguments,DefaultUnavailable,ArgumentRejected,
SignatureDefaultRejected,ReservedKeyandValidationFailedfor the argument layer.
fail_modearbitrates engine failure alone: a denial and a missing principal deny either way. - Relationships resolved by Cedar's entity hierarchy. Nested relationships spanning entities
— user → team → division → project → resource, delegation, on-behalf-of, expiry,
break-the-glass — resolve deterministically rather than through a ladder of applicationif
statements, so a policy names only the far end of a chain and severing one edge ends the access. - A masked, hash-chained audit trail. One record per decision and per admin mutation: the
principal's effective attributes at decision time, the arguments proposed, processed, rejected
and defaulted, the policies that decided, and apolicy_set_hashpinning the record to the
exact policy text. Sensitive values are masked before the append-only write, never at report
time —redact,partial,hmacortokenize, chosen by a Cedar obligation, an
Annotated[..., Sensitive(...)]marker or the configured default, with keyed HMAC the default
because it alone lets an auditor correlate records without the store holding the value; a
principal's own allow-set, deny-set and pattern are masked the same way. Each record's hash
covers its predecessor's, soverify_chaincatches an edited record and a deleted one, and a
record a sink refused is admitted by the next that lands, so an outage cannot read as an intact
window. Tampering is detected, not prevented. - Four context providers, each one extra line of wiring.
FastAPIProvider(pure-ASGI
middleware, anasynccontext dependency, error handlers,protect_app),FlaskProvider
(before_request/teardown_request,protect_app,protect_blueprint),MCPProvider
(middleware aroundtools/call,governed_tool,protect_server) andPydanticAIProvider
(RunContext.depsbridged to ambient context,governed_tool,tool_adapter,tool_prepare,
tool_filter). Each establishes the principal at the boundary and renders a refusal in that
boundary's vocabulary; one built with noidentify=warns once, because its own fallback ends
at something the request supplies. - Per-caller tool manifests, filtered by the decision that enforces the call. A tool is
hidden from a principal if and only if invoking it would be refused. Hiding is a courtesy to
the model and never the control: a client that skips the manifest and calls a hidden tool by
name is refused the same. Arguments policy supplies are re-advertised as optional, so a model
can omit them and receive its own defaults. - A Cedar policy pack, shipped as data. Eight domain-neutral patterns —
rbac,groups,
ownership,delegation,expiry,break-glass,separation-of-duties,jit-elevation—
under apack://DSN, composed through the same union as your own directory. None names a
role, a resource type or an action: the resource declares what it requires and the grant store
supplies the principal's edges. Half the pack forbids, and that half is what contains
rather than permits — reopening an emergency-only resource demands an entitlement, the flag
and a justification, because a permit conditioned on a caller-supplied boolean alone
authorizes anyone. The schema is opt-in,link_templatemakes a time-boxed grant from a Cedar
template, and the pack ships no permit for the admin actions: a library does not get to ship
the rule that hands out authority. - Four extension points, one mechanism. Policy sources, grant stores, audit sinks and context
providers are each aProtocol, a DSN, aregister_*decorator and an entry point, so a
third-party backend is indistinguishable from a built-in. Shipped:file://andpack://
policy sources,memory://andjsonfile://grant stores,memory://andjsonl://audit
sinks. Pass a list instead of one DSN and the composite satisfies the sameProtocol— sources
union, grant stores merge or route by namespace, sinks fan out. - The
sentrykitcommand line, overargparseand no added dependency.policy validate
type-checks a directory against its schema, groups errors by@idand exits non-zero, so it
belongs in CI.grant,revokeandgrantsare the admin surface, each a protected callable
decided by the same governor the ap...
sentrykit 0.1.0rc1
See the changelog for what changed in this release.
Full changelog: https://github.com/namitkewat/sentrykit/blob/v0.1.0rc1/CHANGELOG.md