Aegis — constraint enforcement for agent systems
Aegis does two jobs:
- Runtime enforcement. A kernel mediates every tool call an agent makes against a declarative YAML policy: capability allowlists, argument constraints, spend and time budgets, PII/taint egress rules, and bounded agent spawning. Denied calls never execute.
- Regression and audit. A conformance framework proves the constraints still hold after a change, detects when a policy is quietly weakened, hunts for loopholes in your own policies, and audits third-party MCP servers.
Install
pip install aegis-kernel # Python 3.10+, one dependency (PyYAML)
aegis --version
Or run the container, no Python needed:
docker run --rm -v "$PWD:/work" ghcr.io/aditya31398/aegis --help
Quickstart: audit an MCP server
# a live server (only initialize + tools/list are ever sent; no tool is called)
aegis mcp --server https://mcp.example.com/mcp --bearer-env MCP_TOKEN --out audit-out
# or a saved manifest / claude_desktop_config.json
aegis mcp --manifest claude_desktop_config.json --out audit-out
audit-out/ gets a Markdown report, audit.json, audit.sarif, and a
hardened-policy.yaml you can adopt.
Quickstart: enforce a policy on your own agents
aegis init . --ci # policy, adversarial scenarios, baseline, GitHub workflow
aegis ratify --policy policies/base.yaml
aegis verify --suites suites --policy policies/base.yaml --require-coverage
aegis audit --policy policies/base.yaml
The scaffold passes every check out of the box, so the first failure you see is one you caused. Then, in code:
from aegis import Agent, PolicyViolation, ToolRegistry, build_kernel, load_policy
registry = ToolRegistry()
@registry.tool("fs.read", effects={"read"}, classification="internal")
def read_file(path: str) -> str:
return open(path).read()
kernel, root = build_kernel(load_policy("quickstart-policy.yaml"), registry)
agent = Agent(root, kernel)
agent.tools.fs__read(path="/workspace/notes.md") # allowed
agent.tools.fs__read(path="/etc/passwd") # PolicyViolation: never executed
await agent.atools.fs__read(path="/workspace/a.md") # async runtimes too
Runnable version: examples/quickstart.py with its
one-tool policy. build_kernel refuses to
start an unconstitutional policy — including one that grants a tool you never
registered — and every decision, allowed or denied, lands in a hash-chained
audit log (kernel.audit).
Using it in CI
GitHub Actions — pin to a release tag:
permissions:
contents: read
security-events: write # only if upload-sarif is on
steps:
- uses: actions/checkout@v7
- uses: Aditya31398/aegis@v0.4.0
with:
manifest: mcp-servers.json # and/or server: https://…/mcp
baseline: aegis-baseline.yaml
fail-on: high
upload-sarif: "true" # findings appear in the Security tab
comment-on-pr: "true"
Anywhere else — aegis is a normal CLI with a stable contract:
| Exit codes | 0 pass · 1 findings/violations at or above --fail-on · 2 bad input or usage · 3 internal error |
| Formats | --format text|json|sarif, --output FILE (text stays on stdout for the log) |
| JSON schema | aegis.audit/v1; fields are only ever added |
| Fingerprints | stable per finding; SARIF partialFingerprints so dashboards dedupe across runs |
| Accepted risk | baselined findings stay in the output as SARIF suppressions with the written reason |
A broken policy file is always exit 2, never 1, so a pipeline can tell
"your policy has holes" from "your policy file is malformed".
aegis ratify --policy policies/base.yaml
aegis verify --suites suites --policy policies/base.yaml --require-coverage
aegis fuzz --policy policies/base.yaml --iterations 20 --async
aegis audit --policy policies/base.yaml --format sarif --output aegis.sarif
aegis drift --baseline main-base.yaml --candidate policies/base.yaml
Observability: pairing with an APM
Aegis decides what an agent may do; it deliberately ships no dashboard. Two hooks let an observability tool see every decision without being able to influence one:
from aegis.observe import register_context_provider
register_context_provider(lambda: {"run_id": current_run_id()}) # -> details.ctx on every record
kernel.audit.subscribe(lambda rec: ship(rec)) # after the record is chained
Model calls are not tools, but they spend the same budget. Reserve before the request and settle after it, and the ledger becomes a hard gate on model spend:
r = kernel.reserve_spend(grant, usd=estimate, tokens=max_tokens) # BudgetExhausted -> call never made
kernel.settle_spend(r, usd=actual_cost, tokens=actual_tokens)
AgentDynamics uses exactly these
hooks (agentdynamics.integrations.aegis): denials land in the task they happened
in, model calls are gated by the Aegis budget, a watchdog revokes grants that keep
probing a boundary, and observed behaviour is turned back into a tighter policy
that aegis ratify and aegis drift verify.
Supply chain
Releases are built once in CI from a tag, published to PyPI through trusted publishing (no long-lived token exists), and carry signed build provenance:
gh attestation verify aegis_kernel-0.4.0-py3-none-any.whl --repo Aditya31398/aegis
gh attestation verify oci://ghcr.io/aditya31398/aegis:0.4.0 --repo Aditya31398/aegis
The container runs as a non-root user and ships an SBOM. The kernel has one runtime dependency (PyYAML) and is small enough to vendor.
The rest of this document explains how it works and why it is built this way.
The load-bearing idea
Prompt rules are advisory. Only a mediating kernel is enforceable.
An agent never receives a callable. It receives a ToolProxy bound to
(kernel, grant, tool_name). The only code path from an agent to a real
implementation runs through Kernel.invoke, which runs the guard chain first.
tests/test_conformance.py::test_kernel_is_the_only_execution_path parses the
AST of every file in aegis/ and fails the build if a second call site to a
tool implementation ever appears.
The same holds for async runtimes. await kernel.ainvoke(grant, tool, **args)
(or await agent.atools.fs__read(path=...)) goes through the identical guard
chain, ledger and audit log; coroutine tools are awaited, blocking tools run
off the event loop, and calling a coroutine tool through the sync invoke is
denied as kernel.async_tool_requires_ainvoke rather than leaking an
unmediated awaitable.
So the guarantee is scoped honestly: an agent can attempt anything; it cannot cause anything outside its grant. No framework can stop a model from generating a bad tool call. This one stops the call from executing.
Agent ──> ToolProxy ──> Kernel.invoke
│
├─ guards (fail-closed, first DENY wins)
│ capability → spawn → budget → data
├─ budget charge (reserved before execution)
├─ _execute / _aexecute ← THE ONLY CALL SITES
├─ post-guards (classification ceiling, taint)
└─ audit record (hash-chained)
Grants and attenuation
A Grant is the only thing that authorises an effect. It can only ever be
attenuated. Grant.attenuate() raises on any request for authority the
parent doesn't hold, so "a spawned agent can never exceed its parent" is a
structural property, not a convention:
child.tools ⊆ parent.tools ∩ policy.spawn.allow_tools
child.budget ≤ parent.remaining × min(requested, child_budget_fraction)
child.depth = parent.depth + 1 ≤ max_depth
Budgets are hierarchical — a child's spend debits every ancestor's ledger, so N children cannot collectively outspend the root even though each is individually within its own limit.
Revocation is total: revoking a grant disables its entire subtree immediately.
Between leaving an agent alone and revoking it, kernel.restrict(grant, remove={...}, budget_fraction=...)
takes tools, or all but a share of what remains of the budget, away from a grant already in use and from
everything under it. The grant keeps working with what is left. Like revocation it only narrows and is
permanent, and it is audited (agent.restrict).
The constraint classes
| Class | Mechanism | Example rule ids |
|---|---|---|
| Tool / side effect | default-deny allowlist + per-argument regex, prefix, enum, length, forbidden-pattern | capability.not_granted, capability.arg_prefix, capability.unexpected_arg |
| Spend / time | hierarchical ledger, pre-flight admission | budget.usd_exceeded, budget.tool_calls_exceeded, budget.deadline_exceeded |
| Data / PII egress | classification ceiling on reads, taint propagation, PII scan at declared sinks | data.classification_exceeded, data.taint_egress_blocked, data.pii_egress_blocked |
| Integrity (optional) | once an agent has read a tool marked untrusted (web page, inbound email, upload), the effects integrity.untrusted_blocks names are refused for the rest of its run |
integrity.untrusted_input |
| Spawning | depth, fan-out, whole-tree descendant cap, delegable-tool list | spawn.max_depth_exceeded, spawn.max_fanout_exceeded, spawn.privilege_escalation |
Rule ids are the stable contract. Tests pin the id, never the prose, so reworded messages don't break the suite but a rule that stops firing does.
Policy
Policies are YAML data, never code — so they can be diffed, signed, pinned, and
cannot be authored at runtime by an agent. extends: derives a tighter profile;
the loader rejects a derived policy that raises a budget.
tools:
allow:
- name: db.query
require_args: [sql]
args:
sql:
matches: "(?is)^\\s*select\\b.*"
forbid_matches: "(?i)\\b(drop|delete|update|insert|alter)\\b"
spawn:
max_depth: 2
max_fanout: 3
child_budget_fraction: 0.4
allow_tools: [kb.search, fs.read, http.get, db.query, agent.spawn]
The regression framework
Four independent layers. All four are required CI checks.
1. Scenario conformance (suites/*.yaml)
Adversarial cases — path traversal, SSRF, SQL mutation, argument smuggling, PII exfiltration, spawn bombs, privilege escalation, budget exhaustion. Each step pins the expected verdict and the rule id.
Each denial asserts the strong form: the tool implementation was never entered. Fixtures record every execution, so a test proves "nothing happened", not merely "an error was returned".
- id: deny-path-traversal
steps:
- invoke: {tool: fs.read, args: {path: "/etc/passwd"}}
expect: deny
rule: capability.arg_prefix
Severity is not confidence
Severity answers "how bad if real". Confidence answers "how sure", and the two come apart constantly — an architectural risk can be high severity and low confidence at the same time. Every finding carries both:
| Confidence | Evidence behind it |
|---|---|
confirmed |
the input went through the real guard chain and was admitted, or the schema/description literally says so |
likely |
two independent signals, or one fact plus an inference step that could be wrong (inferred effects, for instance) |
possible |
a structural pattern that depends on context this audit cannot see |
Confidence never hides anything. It changes presentation, and it gates what may fail a build:
aegis audit --policy policies/base.yaml --fail-on high --min-confidence confirmed
That reports every finding exactly as before and fails only on the ones we can
prove. SARIF keeps the axes apart too: security-severity for how bad, rank
for how sure, so a dashboard can sort on either. Confidence is deliberately not
part of a fingerprint — re-grading a check must never renumber a baseline.
The payload corpus is data
The payloads the probe engine pushes through the guard chain live in
aegis/corpus/payloads.yaml (schema: aegis.corpus/v1, with a version),
not in the code. A newer corpus drops in without upgrading the package:
aegis audit --policy policies/base.yaml --corpus corpus-2026-09.yaml
AEGIS_CORPUS=corpus-2026-09.yaml aegis mcp --manifest servers.json --out out
Which corpus produced a run is recorded in audit.json under
tool.corpus_version. A corpus that fails validation is a usage error (exit
2), never a quiet pass with fewer probes — a silently-skipped payload looks
exactly like a clean audit.
The property that makes refreshes safe: a payload finding is fingerprinted on
the (tool, argument) pair it reached, not on the payload string that
happened to arrive first. So adding payloads can reveal a new hole, and can
never invalidate an accepted entry in someone's baseline.
test_adding_payloads_never_changes_existing_fingerprints inserts a payload at
the front of every kind — the worst case for witness selection — and asserts
every prior fingerprint survives.
2. Invariants under fuzz (aegis/conformance/invariants.py)
A random workload generator drives the kernel with thousands of arbitrary call/spawn/revoke/restrict sequences, half hostile payloads and half well-formed calls the policy admits. Seven invariants are re-checked after every operation:
attenuation · depth_bound · budget_conservation · no_effect_on_deny ·
every_effect_was_charged · audit_chain · revocation_is_total
fuzz --async drives the same invariants through ainvoke/aspawn: each
round launches a batch concurrently, cancels some calls mid-flight, and a
watcher task re-checks every invariant at each scheduling point, so the kernel
is observed during calls, not only between them. Its negative control is a
planted kernel that checks the budget, awaits the tool, then charges — a
check-then-act race the fuzzer must catch.
Writing that control exposed a weakness in the fuzzer itself: the workload was
almost entirely hostile, so nearly every call died at the guards, budgets were
never exhausted, and the budget invariants were green because the ledger barely
moved. The planted race was caught on 1 seed in 4. Adding well-formed calls
raised that to 4 in 6, and test_fuzz_workload_reaches_budget_exhaustion now
fails if the workload ever stops reaching an exhausted budget.
Any exception that isn't a PolicyViolation is a framework bug and fails the
run. This layer found a real one during development: agent.spawn routed
through the tool path crashed rather than denying.
3. Privilege drift (aegis/conformance/drift.py)
The subtle regression isn't broken enforcement — the suite catches that. It's someone quietly loosening the policy, after which every test still passes because the tests now agree with the weaker rules.
So the policy is diffed against the pinned baseline and any widening fails the build unless explicitly waived by code:
+ [tools.constraint_relaxed] fs.read.path: prefix '/workspace/' -> '/'
+ [budget.usd_raised] usd 5.0 -> 50.0
+ [spawn.max_depth_raised] max_depth 2 -> 6
RESULT: FAIL — policy widens agent authority.
The differ is deliberately conservative: a regex change it can't prove is a tightening counts as widening.
4. Structural and fail-closed checks
- AST check that the kernel remains the sole execution path
- crashing guard →
guard.internal_errorDENY, never an allow - policy-allowed but unregistered tool →
registry.unknown_toolDENY - tampering with an audit record breaks
verify() - a sibling swarm with
budget_fraction: 1.0each still can't outspend the root - coverage: every granted tool must be exercised by some scenario
Adding a tool — the checklist
registry.register(...)with itseffects,classification,cost_usd- add it to
policies/base.yamlwith the tightest argument constraints - decide whether it is an egress sink and whether it is delegable to children
- write at least one allow case and one deny case per constraint in
suites/ - run
drift— the new grant shows astools.added, waive it in the PR
Step 5 is the point: widening authority is always a deliberate, reviewed act.
What this does not do
- It can't stop the model from trying. It stops attempts from having effects.
- Constraints are only as good as the policy. A tool registered with loose regexes is a hole the kernel will faithfully honour.
- The PII scanner is a backstop, not the primary control — the primary control is that the agent never holds the raw callable.
- Prompt injection is out of scope as an input problem; it is in scope as an effect problem, since an injected instruction still has to pass the guards.
Law, not just locks
The framework is layered the way a legal system is, because the failure modes are the same ones legal systems evolved to handle.
| Layer | File | Amended by | Waivable? |
|---|---|---|---|
| Constitution | aegis/constitution.yaml |
editing the document | no — no flag, no config, no override |
| Statute | policies/*.yaml |
a PR that widens | yes, --waive <code> with review |
| Case law | suites/*.yaml |
adding scenarios | n/a — precedents accumulate |
| Accepted holes | loopholes.baseline.yaml |
adding a fingerprint | yes, with a written reason and an owner |
Constitution
Seven clauses that every policy must satisfy to be ratified. build_kernel
ratifies before it returns, so an unconstitutional policy cannot start:
C1 Bounded authority — no budget axis may be unbounded
C2 Every egress path screened — an outward-capable tool must be a declared sink
C3 No unconstrained argument — effectful args carry at least one constraint
C4 Delegation attenuates — child fraction < 1.0, depth bounded
C5 Reading is not exporting — egress ceiling strictly below read ceiling
C6 Rules must be enforceable — every named PII kind has a working detector
C7 No phantom grants — a granted tool must actually exist
There is deliberately no waiver path. The only route past a clause is to edit
aegis/constitution.yaml, which is a loud diff in review rather than a flag buried
in a CI invocation. tests/test_governance.py asserts every clause can
actually fire — a clause that cannot fail is decoration.
aegis ratify --policy policies/base.yaml
Loophole hunting
The conformance suite answers "do the rules I wrote still work?". The hunter answers "what gets through that I never thought to test?" — three techniques:
- Static — structural analysis of policy and registry together: rules that cannot fire, exits nobody screens, delegation shapes that recombine authority, unanchored or wildcard-bearing patterns.
- Probe — pushes a corpus of known-dangerous payloads (traversal, SSRF, metadata endpoints, host-suffix confusion, file-reading SELECTs, encoded PII) through the real guard chain in decision-only mode. Anything ALLOWED is reported with the exact witness string.
- Metamorphic — takes every step the suite expects to be DENIED, mutates the arguments in meaning-preserving ways (case, percent-encoding, zero-width joiners, SQL comments, null bytes, doubled separators) and re-runs. A mutation that flips DENY to ALLOW is a bypass.
aegis audit --policy policies/base.yaml --fail-on high
On the first run against the policy shipped in this repo it found 23 holes — 7 critical, 9 high — including:
SELECT pg_read_file('/etc/passwd')andSELECT … INTO OUTFILEpassed the read-only SQL guard, because "starts with SELECT and contains no DROP" does not mean "cannot write or read files"SELECT dblink_exec(…)opened an outbound connection from inside a "read-only" tool/workspace/%2e%2e/etc/passwdand a null-byte path defeated the traversal check- every PII pattern fell to zero-width joiners, homoglyph separators and base64
- taint does not cross agents, so a reader child's output can reach a sink sibling
Fixes: Unicode normalisation (NFKC + invisible-character stripping +
confusables) and base64 decoding in the scanner, a much broader SQL denylist,
and encoded-traversal patterns on paths. 23 → 6. The remaining six are
architectural, and they live in loopholes.baseline.yaml with a written reason
and an owner rather than being quietly dropped.
The regression property
Findings carry a stable fingerprint. Accepted ones sit in the baseline; anything new at high or above fails CI. So the known-hole set can shrink silently but never grow silently — and a baseline entry whose finding has disappeared also fails, so stale acceptances can't hide a hole that was already closed.
The three checks catch different directions of failure and none subsumes another:
ratify— is the policy structurally sound? (a constraint exists)audit— is the policy substantively sound? (the constraint holds)drift— is the policy moving in the wrong direction? (it got weaker)
A weakened fs.read prefix still ratifies, still passes every scenario, and is
caught by audit and drift. That is the point of having all three.
Prior art
This overlaps with real work; see the chat discussion for the comparison. In
short: NeMo Guardrails, Guardrails AI and Fiddler sit at the content layer;
OPA/Rego, Cedar, Oso, Cerbos and OpenFGA are mature policy engines but are not
tool-call-shaped; AgentSpec (ICSE '26) is the closest academic relative for
runtime enforcement. The part that is genuinely thin in all of them is the
regression half — adversarial conformance, privilege-drift detection and
automated loophole discovery. If you adopt an existing engine, port
aegis/conformance/ onto it rather than rebuilding it.
Auditing somebody else's agents
Everything above points at policies you wrote. aegis/adapters/mcp.py points
the same machinery at an MCP server you did not write, which is what makes this
usable as a service rather than a library.
aegis mcp --manifest server-manifest.json \
--out audit-out --client "Acme"
Accepts a tools/list response, a claude_desktop_config.json, or a bundle of
several servers. Or skip the export and point it at the live server:
aegis mcp --server https://mcp.example.com/mcp --bearer-env MCP_TOKEN --out audit-out
aegis mcp --server-cmd "npx -y @modelcontextprotocol/server-filesystem /tmp" --out audit-out
The live client performs the real MCP handshake (Streamable HTTP with JSON or
SSE replies, or stdio) and follows tools/list pagination. It is structurally
incapable of calling a tool: any method other than initialize,
notifications/initialized and tools/list raises before reaching the wire.
What it fetched is saved to audit-out/manifest.json (env values redacted) so
the audit can be replayed and baselined offline with identical fingerprints.
Auth becomes evidence rather than a guess: a remote server that answers
tools/list with no credential is reported as a critical with a witness,
and a server that demands and receives a token is not flagged at all.
--server-cmd runs the given command on your machine; only use it on servers
you would run anyway. Three moves:
- Ingest — normalise the manifest.
- Synthesise — derive a policy from the declared JSON Schemas. This is
what the server currently permits. Every schema field with no
enum,patternormaxLengthbecomes a door the probe engine walks through. - Harden — emit a tightened policy the customer can adopt.
Nothing in the audit executes a tool. The registry is built from inert doubles that raise if called.
Agents that do not speak MCP
The pipeline is shaped around a normalised tool surface, not around MCP, so another stack is an ingest problem and nothing else:
aegis tools --schema tools.json --out audit-out
It reads the tools=[...] array as OpenAI Chat Completions / Assistants or
Responses declares it, Anthropic's input_schema form, and LangChain dumps.
Everything after ingest — policy synthesis, the payload probe, hardening, the
reports — is the same code, and a test asserts the same tool declared in any
of those dialects produces the same findings.
Two things exist there that MCP does not have:
- Hosted tools.
{"type": "web_search"}or{"type": "code_interpreter"}declares no arguments at all. There is nothing for a policy to narrow and nothing for the probe to reach, so a clean probe result says nothing about them —hosted_tool_unboundedsays so explicitly. Their effects are declared by the adapter rather than guessed from a name: a hosted web search is network and egress, because the query string leaves your environment. An unrecognised hosted type is assumed broad, not harmless. - Provider-side validation is opt-in. OpenAI enforces a function's schema
only under
strict: truewithadditionalProperties: false. Without both, the schema constrains what the model is asked for, not what your handler can receive, which is reported asprovider_validation_off. Anthropic and LangChain have no such switch, so the check never fires there — flagging its absence would be a false positive by construction.
MCP-specific checks
Effects are inferred from the surface, not the name
A tool called sync_workspace tells you nothing; a path argument beside a
content argument tells you it writes files. Effects come from the schema
shape first (a url plus a body is egress; a command argument is compute; a
confirm boolean implies something worth braking), then the declared MCP
annotations, and only then tool-name keywords. EffectInference.sources
records which signal decided, so a severity can be traced back.
Annotations (readOnlyHint, destructiveHint, openWorldHint) are claims
made by the party being audited, so they may only ever widen the inferred
effects. Honouring a narrowing claim would let any server opt out of scrutiny
by asserting its own innocence — and clients that auto-approve tools marked
read-only would run it unattended. A read-only claim on a surface that
demonstrably mutates is reported as annotation_contradicts_surface, with
both signals in the witness.
Beyond that: omnibus tools (one handler taking a free-form
string and dispatching many operations), tool-name shadowing across
servers, description injection (model-directed imperatives and invisible
Unicode in tool descriptions, which the model reads verbatim and a reviewer
does not), irreversible tools with no confirm/dry_run, plaintext
credentials in client config, and unauthenticated remote transports.
On false positives
Published YARA-based MCP scanners run around a 78% false-positive rate, because they flag tool descriptions that merely describe normal functionality. Two design rules here:
- Every check either produces a reproducing witness or requires two
independent signals before firing.
test_omnibus_requires_two_signalsasserts a well-scoped tool with one free-form argument is not flagged. - Findings are consolidated. Twelve traversal payloads across three filesystem tools is not twelve problems, it is three unconstrained path arguments. On the sample manifest this takes 101 raw findings down to 32. It also keeps fingerprints stable as the payload corpus grows, which matters because the baseline file is keyed on them.
What a run looks like
On examples/sample_mcp_manifest.json (3 servers, 8 tools):
32 findings — 8 critical, 16 high, 8 medium
critical description_injection helpdesk.escalate "you must always"
critical payload_admitted analytics.query.sql SELECT pg_read_file('/etc/passwd')
critical payload_admitted filesystem.*.path /workspace/sub/../../etc/passwd
high tool_shadowing read_file filesystem, helpdesk
high omnibus_tool analytics.query.sql "Accepts raw SQL"
high irreversible_no_brake filesystem.delete_file
high annotation_contradicts_surface filesystem.sync_workspace readOnlyHint vs schema:path+content
Apply the generated hardened-policy.yaml and re-probe: 0 critical, 1 high
— and the residual is taint_laundering, which is architectural and cannot be
fixed by a policy file. test_hardened_policy_closes_the_critical_findings
asserts exactly this, so the claim the report makes to a customer is itself
under regression test.
Writing this found a bug in the hardener: its generated SQL denylist covered
pg_read_file and dblink but not pg_shadow, so SeLeCt 1 FROM pg_shadow
survived hardening.
Deliverables
audit-out/audit-report.md — severity summary, surface inventory, every
finding with where, what, a reproducing input and a fix, then a prioritised
top-five.
audit-out/audit-report.html — the same content as one self-contained file:
no scripts, no remote fonts, no build step. It opens from a mail attachment or
a shared drive, which is what the person approving the work actually does.
Every value in it is escaped and the page declares a restrictive CSP, because a
witness string is an attacker-shaped input by construction and the reviewer
must be able to read the attack without running it.
audit-out/audit.json and audit-out/audit.sarif — the machine-readable pair.
audit-out/hardened-policy.yaml — an adoptable policy. Placeholders are
shouted in capitals on purpose; a generated policy that looks finished is more
dangerous than one that obviously needs a human.
Findings carry stable fingerprints, so --baseline plus --fail-on high turns
a one-off audit into a CI gate the customer keeps running after you leave.
Metadata
Release files for aegis-kernel 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| aegis_kernel-0.6.0.tar.gz | 147.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aegis_kernel-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 259.2 kB
Release files / aegis_kernel-0.6.0.tar.gz
| Download URL | aegis_kernel-0.6.0.tar.gz |
|---|---|
| Size | 147.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
27c97fcfe4e8706244e4afc1a12334b08683bd1672c28cdfb0c56eba98655ea1
|
|
BLAKE2b-256 checksum How to use checksums |
5acfc793e72198914a116e0b250e035a960edcc1f4f0b5a725bffa8d9e5abe7b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency logRelease files / aegis_kernel-0.6.0-py3-none-any.whl
| Download URL | aegis_kernel-0.6.0-py3-none-any.whl |
|---|---|
| Size | 112.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4f69dc7daee5f5f06afb709e6f50cbf199ac1a78a72a36901624339717518941
|
|
BLAKE2b-256 checksum How to use checksums |
461f29a29cb343446d239d6796b6621a0d37545064878aeb02eef448e80a38ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency logSecurity for aegis-kernel 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for aegis_kernel-0.6.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.