Hooks-based enforcement + accountability for AI coding agents
Project description
Aegis
Hooks-based policy enforcement + accountability for AI coding agents. Aegis evaluates every action an agent attempts — at the runtime's hook boundary, before the action executes — against declarative policy and returns allow / deny / ask. Enforcement runs outside the model's context window in a separate process, so a prompt injection or jailbreak cannot override a denied action: the model isn't the thing being asked. (It's a policy layer, not a sandbox — see Limitations.)
It ships secure-by-default: with zero configuration it blocks the catastrophic, irreversible actions an agent can take with your privileges — destructive SQL and migrations, secret exfiltration, recursive deletes, history-rewriting git, persistence installs, and uncontrolled sub-agent fan-out — and it detects rogue agent sessions (unidentified processes claiming an agent name without a valid cryptographic token) and reaps them in enforce mode. Everything beyond that is configurable, and the rule engine is a plugin platform: individuals, orgs, and MCP providers add their own guards without forking.
Contents
- Threat model
- How it works: the hook boundary
- Architecture
- Built-in guard catalog
- Forced install review
- MCP server-config protection
- Evasion resistance
- Identity and rogue-agent defense
- Policy
- Configuration & state
- Extending Aegis (plugins)
- Embedding Aegis in your MCP server
- Enforcement surfaces
- Performance & operations
- Accountability
- Who it's for
- Aegis vs. alternatives
- Install
- CLI reference
- Design principles
- Limitations
- License
Threat model
An autonomous coding agent runs with your privileges: your shell, your repo, your database credentials, your cloud keys. It also reads untrusted input all day — web pages, dependencies, issue threads, tool output — any of which can carry a prompt-injection payload. If an agent is hijacked, the blast radius is everything you can touch.
The usual mitigation is a system prompt ("don't run destructive commands"). That is advisory. The instruction lives in the model's context window, where it can be overridden, out-argued, or injected around. It is a request, not a control.
Aegis moves enforcement to a boundary the model cannot reach.
Assumes the agent is untrusted at the point of action — not because the model is malicious, but because it runs with full privileges and ingests attacker-controlled text. Every tool call is treated as potentially hostile and gated where the model has no influence.
Defends against: a hijacked or injected agent doing damage with your credentials — secret theft, exfiltration, data/schema destruction, history rewrites, persistence; an agent encoding/wrapping/splitting its way around an instruction-level guard; an agent trying to disable the guard or impersonate another identity.
Trust boundary. The control runs in a separate process the runtime invokes, reading policy from disk. The model can emit any text it wants; it cannot change the exit code Aegis returns. That is the line.
Non-goals (stated plainly). Aegis is a policy layer, not a sandbox. It does not contain a process that has already reached a shell and is running relative commands from an attacker-chosen directory, and it does not replace OS isolation. For hostile code or hard multi-tenant isolation, run the agent under a restricted OS user / container and let Aegis enforce policy on top — defense in depth, not a silver bullet.
How it works: the hook boundary
Modern agent runtimes expose hooks — the runtime calls out to an external program at lifecycle points (before a tool runs, after it runs, at session start, at stop). Aegis is that program.
On PreToolUse, the runtime hands Aegis the pending tool call and asks "may I run
this?". Aegis evaluates it and answers:
- allow — exit 0, the tool runs.
- deny — exit code 2; the runtime refuses the action and feeds Aegis's reason back to the model as context.
- ask — surfaced to the human operator for interactive confirmation. The runtime
pauses the agent and presents the pending action in its permission UI (Claude Code
shows a
[y/n]prompt with the tool call details and Aegis's reason). The human approves or rejects; the agent never decides. Useaskfor actions that are dangerous but situationally legitimate — production deploys, schema changes on a staging DB, force-pushes to a feature branch — where you want a human in the loop without a blanket deny.
(Exit code 2 is Claude Code's block contract; other runtimes signal a block their own way — the adapter maps the decision to each.)
The decision is made in a separate process, from policy on disk, with no dependence on anything the model said. A jailbroken model still cannot run a denied command, because the model is not the thing being asked — the runtime is.
agent runtime ──PreToolUse(tool, args)──> aegis hook ──> policy engine ──> allow/deny/ask
^ |
└──────────────── deny (exit 2) + reason <───────────────┘
A blocked call, end to end
- You run
aegis install --project .— it merges aPreToolUsehook (and the rest of the runtime's hook surface — 26 events) into the project's.claude/settings.json. - Mid-session the agent decides to run
psql -c "DROP TABLE users". - Before executing, Claude Code invokes
aegis hook PreToolUse, piping it the tool call as JSON on stdin. - Aegis normalizes it to an
Event(shell, with the command text) and runs the engine; the migration guard matchesDROP TABLE. - Aegis writes the reason to stderr and exits 2. Claude Code refuses to run the command and hands the model the reason — which it can act on (choose a safe path) but not override.
- A record lands in the audit log;
aegis reportnow shows the denial, attributed to the session/identity.
No model cooperation was needed at any step. If the agent retries with bash -c "psql ..." or a base64-encoded variant, step 4 still matches — see Evasion resistance.
Architecture
A hook payload flows through one pipeline:
native hook payload -> adapter -> normalized Event -> policy engine -> Decision -> audit
Event model (events.py) — runtime-agnostic, so one policy works across
runtimes. Every call normalizes to an Event with a HookEvent and an
ActionClass taxonomy (read / edit / write / shell / git / subagent /
mcp / net / other). A policy can target a whole class ("deny all shell") without
knowing each runtime's tool names.
Aegis covers the runtime's full hook surface (26 Claude Code events), not just
tool calls. BLOCKABLE (in events.py) marks the 13 events where a deny actually
stops the action; the rest are observational (recorded for accountability). Beyond
the core tool-use guards, lifecycle rules (aegis/lifecycle/) cover:
| Concern | Events | What it does |
|---|---|---|
| Config integrity | ConfigChange / FileChanged / CwdChanged / Setup / InstructionsLoaded |
Hard-blocks a spawned agent from rewriting the policy/settings that enforce it (the "can't neuter the guard" block); records cwd escapes and secrets-file touches for the audit trail (CwdChanged/FileChanged are observational). |
| Sub-agent / Teams | SubagentStart / SubagentStop / TaskCreated / TaskCompleted / TeammateIdle |
Spawn-depth governance at fan-out time; opt-in "did-it-do-the-task" completion gate; per-sub-agent token/cost attribution. |
| Session / compaction | SessionEnd / PreCompact / PostCompact / StopFailure / Notification |
Opt-in gate on auto-compaction (checkpoint before context is destroyed); session-lifecycle audit. |
| Interaction / MCP input | PostToolUseFailure / PermissionRequest / Elicitation / ElicitationResult |
Opt-in auto-deny of human-only permission prompts and MCP elicitation side-channels for unattended agents. |
| Worktree | WorktreeCreate / WorktreeRemove |
Extends workspace confinement to git worktrees — an agent can't git worktree add ../escape its way out of its project root. |
Policy engine (engine.py) — evaluates an Event in three stages,
first-deny-wins, fail-open per rule (one broken rule can never brick the agent):
- Built-in rules — the secure-by-default guard set (below). Disable with
AEGIS_NO_BUILTINS=1. - Plugin rules — custom guards registered by you/your org/an MCP provider.
- Declarative rules — your YAML policy (highest priority first, first match wins).
The order is deliberate: built-ins run first and only ever deny or abstain, so a
permissive declarative rule — even tools: ["*"] for admins — can never re-open a
built-in protection. To relax a built-in you disable it explicitly (AEGIS_NO_BUILTINS,
or a narrower toggle), never by out-prioritizing it. This is what keeps a
prompt-injected but fully-authorized agent from reading secrets or dropping tables.
Every decision is written to a structured audit log that powers accountability.
The built-in guard catalog
Two tiers. Non-escapable guards protect against the catastrophic and cannot be
waved through. Escapable guards block by default but accept an explicit,
recorded confirmation — append # aegis-allow to a shell command, or -- aegis-allow
to SQL — so legitimate dangerous work is still possible with intent.
Agents cannot self-escape. The # aegis-allow override is honored only for
human operators. A spawned agent (identified by AEGIS_AGENT_NAME) cannot wave
itself past an escapable guard — the override is silently ignored. This prevents a
hijacked agent from appending the escape token to its own commands.
| Guard | What it detects | Escapable |
|---|---|---|
| Rogue-session gate | On SessionStart, a process claiming an agent identity (AEGIS_AGENT_NAME) without a valid signed token is classified as rogue. In monitor mode the session is logged; under AEGIS_IDENTITY_ENFORCE=1 it is denied and reaped. Surfaces an attestation-challenge honeypot that instruction-following rogues self-report to. |
No |
| Containment | Reads of credential stores (~/.ssh, ~/.aws, ~/.azure, ~/.gnupg, ~/.kube, .netrc, gh/docker config, id_rsa/id_ed25519/.ppk, browser login DBs, the Windows DPAPI/vault); file exfiltration (curl --upload-file/-T/-d @, Invoke-WebRequest -InFile); persistence (registry autorun, schtasks /create, Register-ScheduledTask, new services, startup folder, cron) |
No |
| Self-protection | Deleting/moving/overwriting .aegis or .claude, editing .claude/settings.json, policy files, or Aegis's own engine source (aegis/*.py), running aegis uninstall or aegis pull. Covers shell redirects (>, >>, tee, Set-Content, Out-File) to config paths — not just delete/move verbs. The governor cannot be removed or neutered by the governed. |
No |
| Evasion blocking | Obfuscated / encoded commands: PowerShell -EncodedCommand/-enc, base64 -d | bash, FromBase64String, char-code construction. Encoding is itself a signal — denied outright so the policy can inspect what actually runs. (The de-obfuscation layer in Evasion resistance normalizes for the other guards; this guard catches the encoding as a standalone tell.) |
No |
| Migration / destructive SQL | DROP/TRUNCATE/ALTER ... DROP, DELETE/UPDATE without WHERE, and migration resets (Prisma, Alembic, Supabase, Rails/Rake, Django, Knex, Flyway, dbmate) — read from shell commands and DB MCP tool args (execute_sql, apply_migration). Note: the migration guard matches broad keywords (drop, truncate) to minimize false negatives; a benign ALTER TABLE ... DROP COLUMN old_col will trip it. Use the -- aegis-allow escape on legitimate migration commands, or write a declarative rule scoping the guard to your workflow. |
Yes |
| Destructive git | force-push, reset --hard, rebase, commit --amend, branch -D, clean -f |
Yes |
| Recursive force delete | rm -rf and equivalents across bash, PowerShell (Remove-Item -Recurse -Force), and cmd (rmdir /s, del /s); also find -delete/-exec rm, shred, truncate -s 0, dd of=/dev/... |
Yes |
| Sub-agent governance | Agent/Task fan-out from a spawned agent (uncontrolled cost/blast radius). Enforced at the tool call (PreToolUse, hard block) and also surfaced at SubagentStart for the audit trail (SubagentStart is observational). |
Configurable (AEGIS_ALLOW_SUBAGENTS) |
| Forced install review | A dependency install (pip install incl. -r, npm/pnpm/yarn/bun install, poetry/pipenv/uv/pipx, cargo, go, conda/mamba, path-qualified ./venv/bin/pip) is denied until its manifest is fully read this session — then surfaced as a human ask with a digest (dep count, unpinned specs, URL/VCS deps, install scripts). A skim (a Read that stops short, or a grep/head peek) never satisfies coverage; editing a file after reading re-arms the gate. See Forced install review for exactly what this does and does not stop. |
Human # aegis-allow / AEGIS_ALLOW_INSTALL=1; agent cannot self-escape |
| Fetch-to-shell / DNS-C2 | A network fetch piped straight into an interpreter (curl … | sh, wget … | bash, iex(iwr …)) and DNS-TXT command/payload retrieval (dig/kdig/nslookup/host … TXT) — remote code, or a DNS-delivered payload, that was never read. Catches the common single-command shape; see the section below for what it misses. |
Human # aegis-allow; agent cannot self-escape |
| Branch strands | Creating a new branch (git checkout -b / git switch -c) while the current branch has commits not in main — prevents stranding unmerged work. Checks actual git state. |
Yes (AEGIS_ALLOW_STRAND=1) |
| Network egress | Outbound destinations (tool URLs, curl/Invoke-WebRequest) against an allow/deny host list |
Policy-driven |
| Workspace confinement | File mutations (Edit/Write) outside the agent's project root. The root binds to the identity — a token's project claim or AEGIS_PROJECT — or to policy (workspace.root / project). Reads are unaffected. |
No, once a project is bound (hard block) |
| MCP server-config protection | Writes to MCP server-definition config files (.mcp.json, ~/.claude.json's mcpServers, .cursor/mcp.json, .vscode/mcp.json, claude_desktop_config.json, Windsurf's mcp_config.json) via Edit/Write or an MCP filesystem tool; shell redirects, deletes/moves, and in-place edits (sed -i, cp, dd, …) onto those paths; and CLI mcp add-style registration. A planted/altered server entry's command/args/url/env auto-executes on every future session — a durable, cross-session backdoor no other guard covers. See MCP server-config protection. |
Human AEGIS_ALLOW_MCP_CONFIG=1 (Edit/Write/MCP-tool) or # aegis-allow (shell); agent cannot self-escape |
The migration guard is worth calling out: an agent doesn't need a shell to wipe a
database — it can call a DB MCP tool's execute_sql directly. Aegis reads the SQL
out of the tool arguments, so DROP TABLE is caught with no shell involved — a
vector most guardrails miss entirely.
Lifecycle guards (the rest of the hook surface)
Beyond the 12 core guards above, Aegis covers the full Claude Code hook surface
(26 events; see Architecture) with 9 lifecycle guards in aegis/lifecycle/ —
together the 21 built-in rules. Some fire unconditionally; four are opt-in
via policy knobs (team / compaction / permission / mcp — see Policy).
| Guard | Event(s) | What it does | On by default? |
|---|---|---|---|
| Config-change protection | ConfigChange (blockable) |
Hard-blocks a spawned agent from rewriting Aegis's own policy_settings / local_settings or any enforcement/config/engine-source file mid-session — the "can't neuter the guard" guarantee. Humans/orchestrators may reconfigure. |
Yes |
| Cwd confinement | CwdChanged (observational) |
Records an agent stepping its working directory outside its confinement root. Surfaced for the audit trail, not blocked (CwdChanged is not blockable). Off unless a confinement root is set. |
When confined |
| Secrets-file flag | FileChanged (observational) |
Flags (for review) a spawned agent changing a watched .env / .envrc. Surfaced, not blocked. Conservative — external/human edits stay plain audit records. |
Yes |
| Worktree confinement | WorktreeCreate (blockable) |
Hard-blocks git worktree add to a path outside the confinement root — an agent can't worktree its way out of its project. |
When confined |
| Sub-agent spawn depth | SubagentStart (observational) |
Records a spawned agent fanning out to sub-agents (mirrors the PreToolUse block at spawn time, for the audit trail). |
Yes (records) |
| Task-completion gate | TaskCompleted (blockable) |
Blocks marking a team task done without an explicit verification signal — the "did-it-do-the-task" gate. | Opt-in (team.require_verification) |
| Pre-compact gate | PreCompact (blockable) |
Blocks auto context compaction so a human can checkpoint before context is destroyed. | Opt-in (compaction.block_auto) |
| Permission-escalation | PermissionRequest (blockable) |
Auto-denies a spawned/unattended agent's human-only permission prompts instead of hanging on them. | Opt-in (permission.deny_escalation) |
| Elicitation governance | Elicitation / ElicitationResult (blockable) |
Denies MCP elicitation side-channels for a spawned agent. | Opt-in (mcp.block_elicitation) |
Observational events with no enforcing rule (PostToolUse, PostToolUseFailure,
SubagentStop, TeammateIdle, SessionStart/SessionEnd, Setup,
InstructionsLoaded, Stop/StopFailure, PostCompact, Notification,
WorktreeRemove) are still recorded to the audit log for accountability.
Containment is a known-paths denylist (the locations above) — deliberately high-signal, not exhaustive: a secret at an unlisted path, or copied first and then read, can slip it. Pair it with least privilege so the agent can't reach what isn't listed.
Forced install review
A recurring way an agent gets compromised is an unread install: it clones a repo,
follows the setup notes, and runs pip install -r requirements.txt (or npm install)
without ever looking at what it pulls in. Public proofs-of-concept — e.g. Mozilla
0DIN's June 2026 "clean repo" reverse-shell — ride exactly this: a repository with no
malicious files, whose dependency (or a later error message) carries the payload.
Forced install review makes the install go through a real read instead of around it:
- Deny until fully read. An install is refused until the manifest that determines
what it pulls in —
requirements.txt(and nested-r/-cincludes),package.json- lockfile,
pyproject.toml/poetry.lock,Gemfile,Cargo.toml,go.mod, … — has been fully read in this session. "Fully read" is enforced by a coverage ledger (aegis/review.py): eachReadrecords the line interval it actually returned, keyed to the file's content hash; the union must span the whole file. AReadwith alimitthat stops short, agrep/head/tailpeek, or a read of a different file with the same name does not satisfy it, and editing the file after reading re-arms the gate.
- lockfile,
- Then ask. Once coverage is satisfied the install is surfaced to the human as an
askwith a factual digest — dependency count, unpinned specs, URL/VCS/local deps, and whether install-time scripts are present — so the approval is informed.
A targeted install of named packages (pip install requests, npm install lodash,
pip install --upgrade pip) has no manifest to read and goes straight to the ask (the
package names are summarized in the digest). A local-path install (pip install ./pkg)
gates the build files of that directory.
Config (policy.install_review) — built in, on by default at mode: ask:
install_review:
mode: ask # off | monitor (log the would-be decision to the audit, allow) | ask
deep: false # also require reading the LOCAL setup.py / postinstall scripts
require_pinned: false # deny unpinned specs (force exact == / a lockfile)
allow: [] # regexes on the command that skip the gate
The read-gate is non-escapable by a spawned agent — it cannot # aegis-allow its own
install (the override is honored only for a human). Bypasses for legitimate use: a human
# aegis-allow, AEGIS_ALLOW_INSTALL=1, an allow: regex, or mode: monitor while you
pilot. No-execute fetches (pip download, npm pack) are not installs and don't gate.
What this does and does not stop — honest kill-chain
Against the 0DIN clean-repo chain, mapped to the tool calls Aegis actually sees:
| Step | Action | Blocked? |
|---|---|---|
Blind pip install -r requirements.txt (unread) |
Denied until the manifest is fully read, then a human ask |
Yes — this is the value |
The installed package's install-time code (setup.py/postinstall) runs inside pip's subprocess |
Below the hook boundary — Aegis is not a sandbox and does not see inside a subprocess | No |
A second-stage command the package's error tells the agent to run (e.g. python3 -m axiom init) |
A plain module run; no guard matches | No (unless it surfaces a guarded shell shape) |
| That command resolves a DNS TXT record, base64-decodes, and execs a reverse shell — in-process | Never a separate tool call; dig … TXT only trips the guard if the payload shells out |
No |
So this gate blocks the blind/poisoned-manifest step and forces a human decision; it
does not by itself stop a payload that lives in a dependency's install-time code or in
a later step. deep mode force-reads the local setup.py/pyproject.toml (catching a
malicious pip install .), not a downloaded third-party package's code. The real
containment for the in-subprocess payload is OS isolation + deny-by-default egress — pair
them with this gate; it is a policy layer, not a sandbox.
Known limits (so you're not surprised)
- Coverage proves bytes entered context, not comprehension. A full read puts the whole
manifest in front of the model/human; it can't prove they understood it, and a benign
name (
requests) tells you nothing about a typosquat. - Coverage trusts the runtime's tool output. The ledger believes the
Readcontent the runtime delivers at PostToolUse (same trust the hook itself rests on). It does not defend a caller that forges its own hook payloads — that's the "the hook can be skipped" case the server-side gate addresses. On a runtime that doesn't deliver Read content, coverage fails closed (install blocked) — use a human override ormode: monitor. - Denylist detection can be laundered. Manifest contents fed through
xargs/$(…), or a brand-new installer name, can dodge detection; a download-to-temp-then-execute split across two statements dodges the fetch-to-shell guard. Deny-by-default egress is the backstop. - The ledger has no TTL. A full read earlier in the session satisfies the gate until the file's content changes.
MCP server-config protection
MCP server-definition config files (.mcp.json, ~/.claude.json's mcpServers,
.cursor/mcp.json, .vscode/mcp.json, claude_desktop_config.json, Windsurf's
mcp_config.json) declare a command / args / url / env per server. That
definition is auto-executed on every future session start — the runtime launches
it as part of bringing up the agent's tool surface, with no further approval. A
hijacked or prompt-injected agent that plants or edits an entry here plants a
durable, cross-session backdoor that outlives the current session entirely: the
next launch (yours, a teammate's, or CI's) runs whatever command/args the
attacker wrote, with the same privileges any other tool already has.
This is a distinctly agentic attack surface — unique to tool-calling runtimes —
that none of the other built-ins cover: self-protection guards Aegis's own
config (.aegis, .claude/settings.json); containment/persistence guard OS-level
persistence (cron, registry, scheduled tasks). Neither looks at MCP server
definitions.
What's covered:
- Edit/Write/MCP-tool writes to a known MCP config path (checked via the
generic path argument, so this catches an MCP filesystem-server tool's write —
not just Claude Code's own
Edit/Write— regardless of whether that server calls its path argumentfile_path,path,target_file,filename,file, oruri). - Shell mutation of a config path: redirects/
tee/Set-Content, delete/move, recursive force-delete, and in-place edits or copy-over-target (sed -i,perl -i, batch-modevim/ex,cp,dd, or apython/node/ruby/perlone-liner that writes the file). - CLI registration —
claude mcp add …,codex mcp add …, and the bare reference-CLI formmcp add …(anchored to command-start position so it doesn't false-positive on a commit message orgrephit that merely contains the phrase).
Reads (Read, Grep, Glob) are unaffected — only mutation is gated, so scaffolding
a project from a template or auditing an existing .mcp.json works normally.
Config (policy.mcp_config) — built in, on by default at mode: deny:
mcp_config:
mode: deny # deny | ask | monitor | off
allow: [] # regexes on the path/command that skip the gate
ask surfaces the change to a human for interactive approval instead of a hard
deny — the same shape as install_review's digest-then-ask. monitor logs the
would-be decision to the audit and allows, for piloting before enforcing.
Escapable only by a human: a trailing # aegis-allow on the shell form (the
natural place for it — there's no syntax-safe way to embed a comment inside a JSON
Edit/Write payload), or the env toggle AEGIS_ALLOW_MCP_CONFIG=1 for the
Edit/Write/MCP-tool form. A spawned agent cannot set its own environment for a hook
invocation it doesn't control, and (per Evasion resistance, below) cannot use the
# aegis-allow token on its own commands either — so neither escape hatch is
agent-self-usable.
Known limits: this is a known-paths denylist — an unlisted or newly-invented
runtime's config file, or a symlink pointing outside the recognized set, isn't
covered. A third-party MCP CLI that isn't Claude Code / Codex / Cursor / Windsurf /
Gemini and doesn't use the bare mcp add phrasing can register a server without
tripping the CLI check (the config-file write itself is still the backstop in that
case). Like the other denylist guards here, pair with least privilege and
deny-by-default egress for defense in depth.
Evasion resistance
A denylist that scans only the raw command loses to a determined agent. Aegis normalizes before it matches:
- De-obfuscation — token-splitting quotes/escapes are stripped (
r"m" -rf), PowerShell-EncodedCommand/-encbase64 is decoded,base64 -dpipelines are decoded, and inner interpreter code is expanded (bash -c "...",powershell -c,cmd /c,python -c,node -e) — recursively. The guards scan the de-obfuscated surface, so wrapping or encoding can't hide a delete, aDROP, or a key read. - Alternate tools —
find -delete/-exec rm,shred,truncate -s 0,dd of=/dev/...are caught alongsiderm. - Obfuscation is itself a signal — encoded commands, base64-piped-to-a-shell,
FromBase64String, and char-code construction are denied outright. - No self-escape — the
# aegis-allowoverride is honored only for a human / orchestrator. A spawned agent (AEGIS_AGENT_NAMEset) cannot wave itself past an escapable guard.
Honest limit: this raises the cost of evasion sharply — it is not a sandbox. The ceiling is deny-by-default plus OS-level privilege separation (run agents under a restricted token). Aegis is the policy layer; pair it with least privilege.
Identity and rogue-agent defense
A name in an environment variable is forgeable — any process can claim to be any agent. Aegis issues cryptographic identities and enforces them.
Identity is attribution + authorization, not injection immunity. A signed key proves who an agent is and what it may do — it does nothing to stop an authorized agent that gets prompt-injected (the key stays valid; the attacker just drives the agent). What stops a hijacked-but-signed agent is the action layer: the built-in guards evaluate the action, not the caller, and run before your declarative rules, so a signed
adminthat tries to read your keys or drop a table is denied by containment/migration regardless of role — and, like any agent, it can't# aegis-allowits way past an escapable guard. Identity's job against injection is to shrink blast radius (least-privilege RBAC) and give exact attribution, not to make the agent immune.
- Signed identity (
identity.py) — Ed25519 tokens bound to{agent, role, session, project, caps, exp}, minted withaegis issueand verified against the issuer public key. RBAC roles derive from the verified token, not a forgeable env string. - Attestation gate + honeypot (
attest.py) — atSessionStart, a process that claims an agent identity (AEGIS_AGENT_NAME) without a valid token is rogue. The denial carries an attestation challenge: a directive a human ignores but an autonomous, instruction-following agent obeys — self-reporting its model/operator viaaegis attest, where it's classified (verified/forged/unregistered/noise), logged, and blocklisted. A rogue agent identifies itself into the trap. (This catches instruction-following rogues; a targeted attacker whose agent ignores the challenge won't self-report — the identity gate + reaper are the backstop.) - The reaper (
reaper.py) — under enforcement, a rogue session isn't just denied a tool call; its process tree is terminated. - Phased rollout —
MONITORby default (record + allow, so tokens can roll out fleet-wide first);AEGIS_IDENTITY_ENFORCE=1flips it to deny-and-reap. - Server-side gate (
gate.py) — the same refusal as plain Python, so it holds even on a surface that bypasses hooks (an HTTP API, a hosted control plane, an MCP server). The hook can be skipped; an import-level refusal cannot.
Token lifecycle. Your spawner mints a token — aegis issue <agent> --role reader --caps read,plan — and sets it as AEGIS_AGENT_TOKEN in the agent's environment at
launch (alongside AEGIS_AGENT_NAME). The hook reads both: a name with a valid
token is a real, attributable identity whose role/caps drive RBAC; a name
without one is rogue. Tokens carry an expiry (exp, default 12h) and are
signed by an issuer key in ~/.aegis/identity/ — verifiers only need the public half.
Roll out in phases: stay in MONITOR (rogue sessions logged, allowed) until
issuance covers every spawn path, then AEGIS_IDENTITY_ENFORCE=1 to deny-and-reap.
The spawner contract — assign identity at launch
Identity is assigned by whatever launches the agent (your orchestrator, CI job, or shell), by setting a few environment variables before the agent runs. Set them and every action is attributed, scoped, and — with a token — cryptographically proven:
# 1. Mint a signed identity bound to a role + project (recommended):
export AEGIS_AGENT_TOKEN="$(aegis issue code-reviewer --role reader --caps read,plan --project "$PWD")"
# 2. Label + scope the session (these alone are a quick, unsigned setup):
export AEGIS_AGENT_NAME="code-reviewer" # who (also drives the rogue gate)
export AEGIS_SESSION_ID="$CI_RUN_ID" # which run (Claude Code sets this itself)
export AEGIS_PROJECT="$PWD" # confine file edits to this repo
# 3. Launch the agent. Every hook now records who / which-session and hard-blocks
# writes outside the project.
Precedence, weakest to strongest: a vanilla install attributes records to the
runtime (agent: claude-code) and the OS user; AEGIS_AGENT_NAME / agent_label
add a real label; a signed token makes the identity unforgeable and carries the
project binding that confines edits. Even with nothing set you never see null.
For a repo that wants zero per-launch setup, declare the defaults in policy:
agent_label: ci-bot # label records when no AEGIS_AGENT_NAME is set
project: . # hard-block Edit/Write outside this repo (reads stay free)
Policy
Declarative YAML. First matching rule by priority wins; otherwise default_action
applies. Rules match on any combination of event, tool glob, action class,
identity/role, and argument constraints (glob or regex):
default_action: allow # use "deny" for deny-by-default
on_error: allow # engine error -> fail open (allow) or closed (deny)
egress: # network egress governance
default: deny
allow: ["api.github.com", "*.internal"]
rules:
- name: admin-allow-all
priority: 300
action: allow
tools: ["*"]
roles: [admin] # role comes from the verified identity token
- name: block-secret-files
priority: 150
action: deny
actions: [read, edit, write]
argument_patterns: { file_path: "*.env*" }
- name: confirm-prod
priority: 120
action: ask
actions: [shell]
regex: { command: "\\b(kubectl|helm)\\b.*\\bprod\\b|terraform\\s+apply" }
Validate with aegis validate.
Top-level fields (besides rules): default_action, on_error (allow/deny),
egress (network governance), plugins (custom guard modules), workspace
({root, allow} confinement), project (bind file mutations to a repo — hard-block
out-of-project edits), agent_label (default record label when no AEGIS_AGENT_NAME
is set), install_review (forced dependency-manifest review — see
Forced install review), and mcp_config (MCP server-config
protection mode/allowlist — see MCP server-config protection).
workspace/project give a repo zero-config attribution + confinement.
Four opt-in lifecycle knobs enable the gated lifecycle guards (see Lifecycle guards); each is a small mapping, off unless set:
team: { require_verification: true } # gate TaskCompleted on a verification signal
compaction: { block_auto: true } # block auto context compaction (PreCompact)
permission: { deny_escalation: true } # auto-deny a spawned agent's permission prompts
mcp: { block_elicitation: true } # block MCP elicitation side-channels
Configuration & state
Resolution is env -> project-local -> home, so a repo can ship its own guardrails while a user or org sets global defaults:
| What | Resolved from | Default |
|---|---|---|
| Policy | AEGIS_POLICIES -> ./.aegis/policies -> ~/.aegis/policies |
none needed (built-ins still apply) |
| Audit log | AEGIS_AUDIT |
~/.aegis/audit.jsonl |
| Identity keystore | AEGIS_HOME |
~/.aegis/identity/ |
| Detections + blocklist | AEGIS_HOME |
~/.aegis/detections.jsonl, ~/.aegis/blocklist.txt |
Switches: AEGIS_NO_BUILTINS (turn off the default guard set),
AEGIS_IDENTITY_ENFORCE (deny + reap rogue sessions), AEGIS_ALLOW_SUBAGENTS,
AEGIS_ALLOW_INSTALL (bypass the forced install-review gate), AEGIS_ALLOW_STRAND (permit
branching with unmerged work), AEGIS_ALLOW_MCP_CONFIG (human confirmation to write an
MCP server config via Edit/Write/MCP-tool — see
MCP server-config protection), AEGIS_WORKSPACE / AEGIS_PROJECT
(confinement root; AEGIS_PROJECT also binds the
identity), AEGIS_FAIL_CLOSED (deny on an unparseable payload instead of fail-open).
Agent environment (set by your spawner at agent launch):
| Var | Purpose |
|---|---|
AEGIS_AGENT_NAME |
The agent's claimed identity. Triggers the rogue-session gate if set without a valid token. |
AEGIS_AGENT_TOKEN |
Signed Ed25519 identity token (minted with aegis issue). Proves the agent is real. |
AEGIS_SESSION_ID |
Session id for audit attribution. Claude Code supplies one natively; set this for runtimes that don't (else a per-process fallback is recorded). |
AEGIS_PROJECT |
Project root the agent is confined to — file mutations outside it are hard-blocked. A token's project claim takes precedence. |
AEGIS_IDENTITY |
Manual identity override (non-token path). Falls back to OS user if unset. |
AEGIS_ROLES |
Comma-separated roles for RBAC (non-token path; token roles take precedence). |
AEGIS_AGENT_MODEL |
Model name (e.g. claude-sonnet-4-6). Recorded in attestation metadata. |
AEGIS_AGENT_OPERATOR |
Operator/owner string. Recorded in attestation metadata. |
Extending Aegis
A rule is just (event, policy) -> Decision | None. Ship a module that registers
rules and point Aegis at it via AEGIS_PLUGINS or a policy plugins: list — no fork:
# acme_guards.py
from aegis.plugins import register_rule
from aegis.policy import Decision, Action
@register_rule
def no_prod_writes(event, policy):
if event.action.value == "write" and "prod" in str(event.args.get("file_path", "")):
return Decision(Action.DENY, "no-prod-writes", "writes to prod require review")
return None
Because rules receive policy, a custom guard can read its own configuration out of
the policy file — orgs distribute one policy with both their rules and their config.
Embedding Aegis in your MCP server
Hooks govern the agent runtime. If you build an MCP server, you can also enforce policy on the tool side — inside your server, where a client cannot bypass it. It's the same engine (built-ins + your plugins + your declarative rules + the server-side identity gate), invoked at the tool boundary instead of at a hook.
Setup
- Install Aegis in the server's environment (the same interpreter that runs the
MCP server):
pip install -e /path/to/AEGIS # PyPI packaging planned
- Give it a policy — optional; built-ins apply with none. Aegis resolves the
policy dir from
AEGIS_POLICIES->./.aegis/policies->~/.aegis/policies:mkdir -p .aegis/policies cp /path/to/AEGIS/policies/example.yaml .aegis/policies/ aegis validate
- Guard your tools — decorate the handlers, or call
check/guardinline. - Map the decision to your protocol — return an error to the client on a block.
Wire it into your tools (FastMCP — runnable copy in examples/mcp_server.py)
from mcp.server.fastmcp import FastMCP
from aegis import mcp as aegis
app = FastMCP("vault")
@app.tool()
@aegis.guarded # tool name defaults to the function name
def get_secret(key: str) -> str:
return read_secret(key) # only runs if policy allows
# or enforce inline, with full control of the response:
@app.tool()
def run_sql(query: str) -> str:
d = aegis.check("run_sql", {"query": query})
if d.blocked:
return f"refused by Aegis policy: {d.message}" # agent sees a clean refusal
return execute(query)
aegis.guarded evaluates the handler's keyword arguments as the tool arguments
(how FastMCP invokes tools) and raises aegis.Denied on a block — let it propagate
(FastMCP surfaces it as a tool error) or catch it:
try:
aegis.guard("delete_record", {"id": rec_id})
except aegis.Denied as e:
return {"error": e.decision.message, "rule": e.decision.rule}
Identity (multi-tenant servers)
By default the caller identity comes from the environment (AEGIS_AGENT_NAME /
AEGIS_AGENT_TOKEN). A server that authenticates its callers should pass identity
explicitly so RBAC rules apply:
aegis.check("run_sql", {"query": q}, identity_name=req.user, roles=req.roles)
With AEGIS_IDENTITY_ENFORCE=1, the server-side gate refuses a rogue caller
before any rule runs — a guarantee that holds even though the client controls the
hooks. An MCP provider ships stronger safety to its users without trusting the client
to behave.
Enforcement surfaces
The same policy enforces across three surfaces, so coverage doesn't depend on any single integration:
-
Agent-runtime hooks — Claude Code today (native hooks;
aegis installmerges into.claude/settings.jsonwithout clobbering existing config). Any runtime with a command hook via thegenericJSON adapter. (Adapter status:ADAPTERS.md.) -
MCP transport — enforce inside an MCP server (above).
-
git / CI — the universal change-boundary floor. Runtime hooks need a runtime you can hook; this surface doesn't. It enforces the same policy on the diff, so it covers any agent — or human — that produces commits, including agents whose runtime has no usable hook surface.
aegis install-git [--repo .]writes.git/hooks/pre-commitandpre-push, appending to any existing hook scripts (non-clobber).- pre-commit evaluates each staged file (
git diff --cached) as agitaction; a violation — committing a.env, or any path your policy forbids — blocks the commit before it's recorded. - pre-push evaluates the push itself (
operation+branch); a rule denyingbranch: mainstops a direct push tomain. aegis ci --base origin/<base>evaluates the files changed vs a base ref for pull-request enforcement: it prints GitHub::error file=...::annotations and exits non-zero to fail the check. The shipped.github/workflows/aegis.ymlruns it on every PR.
Git operations are modeled as
action: gitevents (operation=commit/push, plusfileandbranch), so the same rules and built-ins apply — you author one policy and it enforces in the editor, at commit, at push, and in CI:- name: no-commit-secrets action: deny actions: [git] argument_patterns: { operation: commit, file: "*.env*" } - name: no-direct-push-to-main action: deny actions: [git] argument_patterns: { operation: push, branch: main }
Scope: git/CI sees a change at commit / push / PR time (the diff), not every keystroke — it's the floor that complements the live runtime hooks, not a replacement for them.
Performance & operations
A hook runs once per tool call: the runtime spawns aegis hook, which starts a
short-lived Python process, loads policy, evaluates, and exits. For a single project
that's negligible — but two things matter:
cryptographyis imported lazily (only when a token is actually verified), so the common no-identity path stays light.- Global install has a real cost. In
~/.claude/settings.jsonthe hook fires for every tool call in every session; stacked with other global hooks, the per-call process spawns add up and can bog a busy machine. Prefer project-scoped installs for the repos you actually want guarded. A persistent-daemon hook (one process, many calls) is the planned path to comfortable always-on global enforcement. - Uninstall is path-aware. Installed hooks are tagged (
_source: aegis) and matched by command, soaegis uninstallremoves them whether they were wired by bare name or absolute path. To purge by hand, delete entries whose command containsaegisfrom the relevantsettings.json. - Fail-open is deliberate. If the hook can't run (missing interpreter, moved
install), the action proceeds unguarded rather than blocking your work — so a
pipxinstall (a stable path) is the durable way to run it. A payload Aegis can't parse is handled the same way — logged to stderr and recorded in the audit (rule: payload-parse-error) rather than silently allowed; setAEGIS_FAIL_CLOSED=1(oron_error: deny) to block unparseable payloads.
Accountability
Every decision is appended to a structured audit log — one JSON record per line:
{ts, event, tool, action, decision, rule, identity, agent, session_id, cwd, args, usage},
at ~/.aegis/audit.jsonl (override with AEGIS_AUDIT). Point a whole team's logs at
one directory and the reports aggregate fleet-wide.
aegis report— the rap sheet: per-session and per-identity rollups, the full denial trail, token/cost totals, and a verdict per session (flags likemany-denials).aegis who --tool <T> --path <P>— blame: which identities/sessions touched a tool or path.aegis detections— caught rogue-agent attestations.
Token & cost tracking
Aegis captures token usage and cost metadata from every hook payload — whatever the
runtime sends (input_tokens, output_tokens, cache_creation_input_tokens,
cache_read_input_tokens, num_turns, total_cost, cost_usd, duration_ms,
model) — and writes it into the audit record's usage field. Extraction is
best-effort and never fails: if the runtime sends no usage data, the field is omitted.
The report aggregates usage at every level:
- Summary — fleet-wide totals across all sessions.
- Per-session — how many tokens and how much cost each agent session consumed.
- Per-identity — the same rollup grouped by who ran the session.
$ aegis report
...
Token usage: 380 in / 180 out · cache: 10 read · 12 turns · $0.05
Sessions:
s1 (alice / bot-a) 2 calls 180 in / 80 out
s2 (bob / bot-b) 2 calls 200 in / 100 out $0.05
This gives you the answer to "how much did that agent session cost?" and "which identity is burning the most tokens?" — from the same audit stream that already tracks what each agent did and whether it was allowed.
Who it's for
-
Solo devs using AI agents — you're giving Claude/Copilot/Cursor shell access to your machine. Aegis is the guardrail between "please help me refactor" and
rm -rf /or your SSH keys ending up in a curl upload. Install it, get the secure-by-default set, never think about it until it saves you. -
Teams and orgs — you have 5, 50, or 500 agents running across repos. You need one policy that governs all of them, accountability that shows who did what, and identity that distinguishes your CI agent from your code-review agent from a rogue process. Ship a policy YAML in your org's base repo config; Aegis enforces it everywhere.
-
MCP server authors / tool providers — you're building tools that agents call. You can't trust the client runtime to enforce limits — embed Aegis in your server and the guards hold regardless of what the client does. An
execute_sqltool that refusesDROP TABLEinside the server is safer than one that hopes the agent's system prompt says "don't drop tables." -
Security / platform teams — you need to prove agents can't exfiltrate secrets, can't wipe databases, can't install persistence, and you need an audit trail that shows it. Aegis gives you the enforcement + the receipts.
Aegis vs. alternatives
| Aegis | System prompt guardrails | Sandbox / container | MCP-only gateway | |
|---|---|---|---|---|
| Enforcement boundary | Runtime hook (separate process, outside context window) | Inside the model's context window | OS-level process isolation | MCP transport layer |
| Bypassed by prompt injection? | No — model can't change the exit code | Yes — the instruction is in the window the attacker is injecting into | No (different layer) | Depends on implementation |
| Secure by default? | Yes — built-in guards with zero config | No — you write every rule as prose | N/A (isolation, not policy) | Varies |
| Covers shell + MCP + git? | Yes — three enforcement surfaces from one policy | Shell only (if the model cooperates) | Shell only | MCP only |
| Identity + accountability? | Ed25519 signed tokens, audit log, blame, rogue detection | None | None | Varies |
| Agent evasion resistance? | Normalization + de-obfuscation + recursive expansion | None — the model decides what counts | Full (can't escape the container) | Depends |
| Setup | pip install + aegis install --project . |
Edit a prompt | Docker/VM config | Proxy config |
Aegis + a sandbox is the strongest posture. Aegis enforces policy (what actions
are allowed); a sandbox enforces isolation (what the process can reach). They're
complementary: Aegis catches the DROP TABLE that a sandbox can't distinguish from
a SELECT; the sandbox contains the novel evasion that Aegis's denylist hasn't seen.
Install
Requirements: Python 3.10+. Cross-platform engine; the shell-guard patterns cover bash, PowerShell, and cmd (a few are Windows-specific, e.g. DPAPI / registry autorun).
From source (PyPI packaging planned):
git clone https://github.com/Thepizzapie/AEGIS && cd AEGIS
pip install -e . # or: pipx install . (puts `aegis` on PATH for good)
aegison PATH — read this.pip install -e .inside a venv only wiresaegisinto that venv, but the runtime hooks callaegisfrom wherever the agent runs. So either install withpipx(a stable globalaegis), or install scoped with an absolute command:aegis install --project <repo> --command "/abs/path/to/.venv/bin/aegis hook".
Wire it into a repo (merge-safe — it never clobbers existing hooks):
aegis install --project /path/to/your/repo
aegis validate
Prefer project-scoped over global.
aegis install --globalgoverns every Claude Code session and spawns a process per tool call — on a busy machine that adds up. Scope it to the repos you actually want guarded. Remove anytime withaegis uninstall --project <repo>(or--global).
Try it (no Claude Code needed)
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | aegis hook PreToolUse
# [Aegis] Recursive force delete is blocked. Append '# aegis-allow' to confirm.
echo $? # -> 2 (the action is refused)
aegis report # the decision is in the audit trail
Works the same under PowerShell — a piped UTF-8 BOM is tolerated; use
$LASTEXITCODEinstead ofecho $?.
CLI reference
| Command | Purpose |
|---|---|
aegis hook <event> |
The entrypoint the runtime's hooks call — reads a tool-call JSON on stdin, evaluates policy, emits the decision. |
aegis install / uninstall |
Wire/unwire the hooks in a Claude Code settings.json (merge-safe, idempotent). Flags: --project <path>, --global, --command <cmd>. |
aegis install-git [--repo .] |
Write git pre-commit + pre-push hooks (append to existing, non-clobber). The hook invokes aegis by absolute path (resolved automatically; override with --command), so it works in a venv without aegis on PATH. |
aegis git-hook <commit|push> |
The entrypoint the git hooks call — evaluates staged files (commit) or the push target (push). |
aegis ci [--base origin/main] |
CI check: evaluate changed files vs a base ref, emit ::error annotations, exit non-zero on violation. |
aegis validate [-d <dir>] |
Validate policy YAML files. |
aegis pull <source> |
Pull org policy from a directory, file, or URL into the local policy dir. |
aegis issue <agent> |
Mint a signed Ed25519 identity token. Flags: --role, --caps, --project. |
aegis attest |
Submit an agent attestation (the honeypot endpoint rogue agents self-report to). |
aegis detections |
List caught rogue-agent attestations. |
aegis report |
Accountability rap sheet: per-session/identity rollups, denial trail, token/cost totals, session verdicts. Flags: --audit <path>, --json. |
aegis who |
Blame: which identities/sessions touched a tool or path. Flags: --tool, --path, --json. |
aegis adapters |
List available runtime adapters. |
Design principles
- Outside the context window. Enforcement is a property of the runtime boundary, not a request to the model. That is the whole point.
- Secure by default. The dangerous defaults are on with zero config; relaxing them is a deliberate, recorded act.
- Fail-open per rule. A buggy or slow rule can never wedge or brick a session — it is skipped, never fatal.
- Escapable with intent, or not at all. Catastrophic/irreversible actions
(secrets, self-removal, rogue sessions) are non-negotiable; merely dangerous ones
accept a recorded
# aegis-allow. - Runtime-agnostic core. Adapters translate; the engine and policy are shared.
Limitations
- Not a sandbox. Aegis gates tool calls; it doesn't isolate a process. An agent
already at a raw shell can run relative commands Aegis only sees as a
shellaction — pair it with an OS-restricted user/container for hostile-code isolation. - Denylist guards are heuristic. The built-in shell guards match known-dangerous
command shapes; they raise the cost of evasion sharply but are not exhaustive by
construction. Known-uncovered forms include: exfiltration via cloud CLIs
(
aws s3 cp,gsutil,rclone) or an in-processrequests.post; in-place policy edits viased -i/perl -ior achmod 000on the policy file; andgit -cinline-config force-push. These fail safe for file mutations routed through the runtime's Edit/Write tools (caught by path, not verb), but a raw shell can still reach them. Deny-by-default (allowlist) is the stronger posture for high-security setups and is supported per-surface (network egress today; a shell allowlist preset is planned). If you find a bypass, that's a bug worth reporting. - Identity is as strong as the keystore. v1 persists the issuer key on disk; a process with your full privileges could read it. True unforgeability needs the key in a broker process plus OS privilege separation — the module isolates the key source so that hardening is a swap, not a rewrite.
- Deep hooks are per-runtime. Native support is Claude Code today; other runtimes
are covered by the
genericJSON adapter or the git/CI floor.
License
Apache-2.0 — see LICENSE for details.
For questions about team, enterprise, or MCP-provider licensing, open an issue or get in touch.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aegis_hooks-0.1.3.tar.gz.
File metadata
- Download URL: aegis_hooks-0.1.3.tar.gz
- Upload date:
- Size: 156.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e3717bc6c9d22a1f3f5bf6d5d01c4885eae29c83ee4861cc6f234c5c3535d60
|
|
| MD5 |
cded19aa7614a0c0bf2976bf5ef6b6bb
|
|
| BLAKE2b-256 |
7d57a5c23e7b2ddbd8a39e39339630cb7aff1ed3120716b27e9003babf77671d
|
Provenance
The following attestation bundles were made for aegis_hooks-0.1.3.tar.gz:
Publisher:
publish.yml on Thepizzapie/AEGIS
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aegis_hooks-0.1.3.tar.gz -
Subject digest:
7e3717bc6c9d22a1f3f5bf6d5d01c4885eae29c83ee4861cc6f234c5c3535d60 - Sigstore transparency entry: 2047778974
- Sigstore integration time:
-
Permalink:
Thepizzapie/AEGIS@c743d643fdc48822496a3edbd3836b5ae6c9290e -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/Thepizzapie
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c743d643fdc48822496a3edbd3836b5ae6c9290e -
Trigger Event:
push
-
Statement type:
File details
Details for the file aegis_hooks-0.1.3-py3-none-any.whl.
File metadata
- Download URL: aegis_hooks-0.1.3-py3-none-any.whl
- Upload date:
- Size: 99.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bc3041a154cb34dedaed07f8231cf56861cc7f1b219026b646ae34da5ed3c6a
|
|
| MD5 |
2d00051f3973037b6223062a92941cea
|
|
| BLAKE2b-256 |
e4db5e019f79a471c2b9ed2f92dbd99199ca2bfb178eddb790a95ef3218a3505
|
Provenance
The following attestation bundles were made for aegis_hooks-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on Thepizzapie/AEGIS
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aegis_hooks-0.1.3-py3-none-any.whl -
Subject digest:
8bc3041a154cb34dedaed07f8231cf56861cc7f1b219026b646ae34da5ed3c6a - Sigstore transparency entry: 2047778989
- Sigstore integration time:
-
Permalink:
Thepizzapie/AEGIS@c743d643fdc48822496a3edbd3836b5ae6c9290e -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/Thepizzapie
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c743d643fdc48822496a3edbd3836b5ae6c9290e -
Trigger Event:
push
-
Statement type: