An action firewall for AI agents
Project description
Enact
You just gave an LLM access to real APIs. What happens when it does something stupid?
It already has. Replit's agent deleted a production database. Amazon Kiro caused a 13-hour AWS outage. Claude Code ran rm -rf on a home directory. These weren't bugs — the agents did exactly what they were told. The problem: nothing was checking whether they should.
Enact is the missing layer between your agent and the real world:
- Block dangerous actions before they fire — Python policies run before anything executes. Agent tries to push to main? Blocked. Tries to delete without a WHERE clause? Blocked.
- Execute deterministically — LLMs hallucinate. They call functions that don't exist, use wrong argument names, get column names wrong. Plain Python workflows do exactly what you wrote — they can be unit tested, reviewed in a PR, and
git diff'd. LLM-generated actions cannot. - Prove what happened — Every run (PASS or BLOCK) writes a cryptographically-signed JSON receipt: who ran what, which policies passed, what changed.
- Roll back in one call — When your agent wipes a database table, deletes the wrong branch, or trashes two hours of work,
enact.rollback(run_id)brings it all back. Deleted rows restored. Branches recreated. PRs closed.
pip install enact-sdk
Quickstart (30 seconds)
git clone https://github.com/russellmiller3/enact
cd enact
pip install enact-sdk
python examples/quickstart.py
That's it. Three runs — one BLOCK, one PASS, one ROLLBACK — with signed receipts.
Want the full show? python examples/demo.py runs a 3-act scenario: an agent blocked from pushing to main, a normal PR workflow, and a database wipe rolled back in one command. No credentials needed.
Already have an agent? Migration takes 10 minutes.
Your agent's reasoning and planning logic doesn't change. You're adding a safety layer between it and your systems. Same calls, same results — now with policy enforcement, a signed audit trail, and rollback.
Three steps:
- Register your systems — swap your existing SDK clients for Enact connectors (same credentials, now policy-gated)
- Move your guard logic — any
if/elsechecks you write become Python policy functions, or use our 24 built-in ones - Replace direct calls —
tool.do_thing()becomesenact.run()
Before (your agent today):
import github_sdk, psycopg2
# direct call — no policy check, no audit trail
github_sdk.create_pr(repo="myorg/app", branch="agent/fix-123", title="Fix bug")
# no WHERE protection — deletes every row
db.execute("DELETE FROM sessions")
After (wrapped with Enact):
from enact import EnactClient
from enact.connectors.github import GitHubConnector
from enact.connectors.postgres import PostgresConnector
from enact.policies.git import dont_push_to_main
from enact.policies.db import dont_delete_without_where
# one-time setup — replaces your SDK clients
enact = EnactClient(
secret="...",
systems={
"github": GitHubConnector(token="..."),
"postgres": PostgresConnector(dsn="postgresql://..."),
},
policies=[dont_push_to_main, dont_delete_without_where],
)
# same intent — now policy-gated, receipt-backed, rollback-able
result, receipt = enact.run(
workflow="agent_pr_workflow",
user_email="agent@company.com",
payload={"repo": "myorg/app", "branch": "agent/fix-123"},
)
Works with LangChain, CrewAI, OpenAI, Claude tool_use — any framework that can call a Python function. Your agent's prompting and reasoning stay exactly as-is.
Core Concepts
Think of Enact like a foreman supervising an AI carpenter. The carpenter is capable and fast, but needs oversight. When the carpenter says "I want to tear down this wall":
- Permit check — Before any tool is picked up, the foreman checks the plans. Load-bearing? Utilities inside? Approved? If not: work stops, written reason recorded.
- Blueprint — If approved, the carpenter follows exact step-by-step instructions — not just "tear down the wall" but each specific action in order. No improvising.
- Work log — A signed record of every nail pulled, every stud removed, exact before-and-after state. Cryptographically sealed so it can't be altered later.
- Change order — If the carpenter tore down the WRONG wall, the foreman issues a change order. Enact uses the work log to reverse every step and put it back.
The Four Pieces
| Piece | What it is | Analogy |
|---|---|---|
| Policy | A Python function that returns pass/fail | The permit check |
| Workflow | A Python function that does the actual work | The blueprint the carpenter follows |
| Receipt | A signed JSON record of what happened | The signed work log |
| Rollback | One call that reverses an entire run | The change order + teardown |
How They Fit Together
Agent wants to do something
|
v
+----------+
| POLICIES | <-- "Is this approved?" (permit check)
+----------+
|
PASS | BLOCK --> Receipt (denied + reason)
v
+-----------+
| WORKFLOW | <-- "Follow the blueprint, step by step"
+-----------+
|
v
+----------+
| RECEIPT | <-- "Signed work log — what happened, what changed"
+----------+
|
if needed:
v
+----------+
| ROLLBACK | <-- "Change order — reverse every step using the work log"
+----------+
Why This Matters
These weren't bugs — the agents did exactly what they were told. The problem was no permit check, no work log, no way to undo it:
| Incident | What Happened | Source |
|---|---|---|
| Replit | Agent deleted a production database containing data for 2,400+ executives | Fortune, Jul 2025 |
| Amazon Kiro | Agent deleted an EC2 environment → 13-hour AWS outage | Awesome Agents, Feb 2026 |
| Claude Code | Agent ran rm -rf ~/ — wiped developer's entire home directory |
ByteIota, Dec 2025 |
How It Works
Prerequisite: The Connector
WHY: Your agent shouldn't call GitHub directly. You want a middleman that (a) limits what the agent can do and (b) records what actually happened. That's the Connector.
A Connector is a pre-built class that wraps an external system. You create one, hand it to Enact, and Enact passes it to your workflow. You never call GitHub (or Postgres, or the filesystem) directly anymore — you call the connector.
Think of it like handing a contractor a limited toolbox before you leave for work. The toolbox only contains the tools you specifically put in it. If the contractor hallucinates and decides to demolish a load-bearing wall — too bad, there's no sledgehammer in the box.
Here is how you create and use a connector:
from enact.connectors.github import GitHubConnector
# Create the connector — you only allow the two actions you actually need
gh = GitHubConnector(
token="ghp_...", # Your GitHub Personal Access Token
allowed_actions=["create_branch", "create_pr"] # ONLY these methods can be called
)
# Now call an action on it
result = gh.create_branch(repo="owner/repo", branch="agent/fix-149")
# Every action returns an ActionResult — a mini-receipt for that one action
print(result.success) # True or False
print(result.output) # {"branch": "agent/fix-149"}
Why allowed_actions matters: Policies are your smart rules — they enforce your business logic and the scenarios you anticipated. allowed_actions is your hardcoded floor: even if your agent tries something you never thought to write a policy for, it simply can't execute an action that isn't on the list. Policies handle what you thought of. allowed_actions handles everything you didn't.
# This is what happens if the agent goes rogue:
gh.delete_branch(repo="owner/repo", branch="main")
# -> PermissionError: Action 'delete_branch' not in allowlist
Enact ships connectors for GitHub, Postgres, the filesystem, and Slack. You don't write these — you import and configure them.
Prerequisite: The Context
The WorkflowContext is the "bag of data" that travels through the entire system — passed to every policy check and every action.
Think of it like a delivery package. The context contains:
- Who sent it (
user_email) - What they want done (
payload) - The tools they can use (
systems)
Here is what a WorkflowContext looks like in memory:
# Enact builds this automatically — you never create it manually.
# It's shown here so you understand what your workflow receives.
context = WorkflowContext(
user_email="agent@company.com", # Who is making the request
payload={ # The data the agent wants to act on
"repo": "owner/repo",
"branch": "agent/fix-149",
},
systems={ # The connectors, keyed by name
"github": GitHubConnector( # The actual connector you configured
token="ghp_...",
allowed_actions=["create_branch", "create_pr"],
),
},
)
Step 1: Define what your agent should do
WHY: Instead of your agent running arbitrary code against GitHub, you give it a script to follow — a plain Python function. Enact runs that function. This way, every action is recorded, every failure is caught, and you can roll back the whole thing.
A workflow is a Python function that takes a context (the bag from above) and returns a list of ActionResult objects — one per action taken. If you're coming from Semantic Kernel, LangChain tools, or MCP — a workflow is your skill, hardened: same callable interface, plus policy enforcement, a signed receipt, and rollback.
from enact.models import WorkflowContext, ActionResult
def agent_pr_workflow(context: WorkflowContext) -> list[ActionResult]:
# Pull the connector and payload data out of the context bag
gh = context.systems["github"] # The GitHubConnector you configured
repo = context.payload["repo"] # "owner/repo"
branch = context.payload["branch"] # "agent/fix-149"
results = []
# Take the first action — create the branch
result1 = gh.create_branch(repo=repo, branch=branch)
results.append(result1) # Keep a running log of everything that happened
# Stop early if it failed — no point creating a PR for a branch that doesn't exist
if not result1.success:
return results
# Take the second action — open the pull request
# f"Agent: {branch}" is Python string interpolation: becomes "Agent: agent/fix-149"
result2 = gh.create_pr(repo=repo, title=f"Agent: {branch}", body="Automated PR", head=branch)
results.append(result2)
return results # Enact signs this list into a receipt
Step 2: Define the policies it should follow
WHY: The workflow does whatever you tell it to. Policies decide whether it should run at all. They run first, before any action fires. If any policy fails, the whole run is blocked and you get a receipt explaining why.
A policy is a plain Python function — no LLMs, no magic. It reads the context and returns pass or fail with a reason.
Here's a concrete example. The standard engineering rule is: no one pushes directly to main. Instead, changes go into a separate branch, get reviewed by a human in a Pull Request (PR), and only then get merged. This gives you a checkpoint before anything goes live.
Agents break this rule constantly. They push directly to main because no one told them not to — and because they can. The Amazon Kiro incident was exactly this pattern: an agent made a direct infrastructure change with no review step, and caused a 13-hour AWS outage. This policy is the guardrail: if the agent tries to target main, the run is blocked before any code is touched.
from enact.models import WorkflowContext, PolicyResult
def dont_push_to_main(context: WorkflowContext) -> PolicyResult:
branch = context.payload.get("branch", "")
branch_is_not_main = branch.lower() not in ("main", "master")
return PolicyResult(
policy="dont_push_to_main",
passed=branch_is_not_main,
reason="Branch is not main/master" if branch_is_not_main else f"Direct push to '{branch}' is blocked",
)
How the check works — three logical steps:
┌─────────────────────────────────────────────────────────┐
│ STEP 1: Read the branch name from the agent's request │
│ context.payload.get("branch", "") --> "main" │
└────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ STEP 2: Is this branch safe to push to? │
│ branch_is_not_main = branch not in ("main","master") │
│ │
│ "agent/fix-149" --> branch_is_not_main = True ✅ │
│ "main" --> branch_is_not_main = False 🚫 │
└────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ STEP 3: passed = branch_is_not_main │
│ │
│ True --> PASS ✅ │
│ False --> BLOCK 🚫 │
└─────────────────────────────────────────────────────────┘
You don't need to write most policies yourself — Enact ships 24 built-in ones. See Built-in Policies below.
Step 3: Wire it all up and run
WHY: Now you hand everything to EnactClient — your connectors, policies, and workflows. Then you call enact.run() the same way your agent would. Enact handles the policy check, the execution, and the receipt.
from enact import EnactClient
from enact.connectors.github import GitHubConnector
from enact.workflows.agent_pr_workflow import agent_pr_workflow
from enact.policies.git import dont_push_to_main, require_branch_prefix
enact = EnactClient(
systems={
"github": GitHubConnector(
token="ghp_...", # Your GitHub PAT
allowed_actions=["create_branch", "create_pr"], # Only these are allowed
)
},
policies=[
dont_push_to_main, # A plain policy function (defined above)
require_branch_prefix("agent/"), # A policy *factory* — calling it with "agent/"
# returns a configured policy function
],
workflows=[agent_pr_workflow], # Register the workflow by passing the function
secret="your-secret-here", # Min 32 chars. Or: export ENACT_SECRET="..." in shell
)
# This is what your agent calls. It returns two things:
result, receipt = enact.run(
workflow="agent_pr_workflow", # Which workflow to run (must be registered above)
user_email="agent@company.com", # Who is making the request (for audit trail)
payload={"repo": "owner/repo", "branch": "agent/fix-149"}, # Data for the workflow
)
print(result.decision) # "PASS" or "BLOCK"
print(receipt.run_id) # UUID — use this to look up or roll back the run
Step 4: Read the receipts
Every run — PASS or BLOCK — writes a signed JSON receipt to receipts/:
{
"run_id": "a1b2c3d4-...",
"workflow": "agent_pr_workflow",
"user_email": "agent@company.com",
"decision": "PASS",
"policy_results": [
{
"policy": "dont_push_to_main",
"passed": true,
"reason": "Branch is not main/master"
},
{
"policy": "require_branch_prefix",
"passed": true,
"reason": "Branch 'agent/fix-149' has required prefix"
}
],
"actions_taken": [
{ "action": "create_branch", "system": "github", "success": true },
{ "action": "create_pr", "system": "github", "success": true }
],
"timestamp": "2026-02-26T03:30:00Z",
"signature": "hmac-sha256-hex..."
}
Verify a receipt hasn't been tampered with:
from enact.receipt import verify_signature
is_valid = verify_signature(receipt, secret="your-secret")
Receipt Browser (local UI)
Browse, filter, and verify your receipts locally — no cloud required.
enact-ui # serves receipts/ on http://localhost:8765
enact-ui --port 9000 # custom port
enact-ui --dir /path/to/receipts # custom directory
enact-ui --secret YOUR_SECRET # enables signature verification in the UI
The browser shows every run (PASS / BLOCK / ROLLED_BACK), lets you click into the full JSON, and highlights invalid signatures. Dark mode toggle included. Zero extra dependencies — ships with enact-sdk.
Step 5: Rollback (if something goes wrong)
WHY: Say the agent_pr_workflow from Step 1 ran — it created agent/fix-149, opened a PR, and merged it straight to main by mistake. You need to undo all three steps. One call.
rollback() does four things in order:
- Loads the receipt by
run_id— looks upreceipts/a1b2c3d4-....json - Verifies the signature — if the receipt was tampered with, rollback refuses to run
- Walks
actions_takenin reverse — last action first, so nothing is orphaned - Calls the undo action for each step and writes a new rollback receipt
Here's what "in reverse" looks like for a workflow that created a branch, opened a PR, then merged it:
Original run (forward): Rollback (reverse):
Step 1: create_branch Step 3 undone: revert_commit (new commit on main)
Step 2: create_pr → Step 2 undone: close_pr
Step 3: merge_pr Step 1 undone: delete_branch
Why reverse? merge_pr happened last — you have to undo it first before closing the PR makes sense. Reverse order preserves the dependency chain.
revert_commit is git revert -m 1 <sha> under the hood — it adds a new commit to main that restores its pre-merge state. Safe on protected branches; no force-push needed. The merge SHA is captured automatically in the receipt when merge_pr runs.
# receipt.run_id came from the enact.run() call in Step 3
rollback_result, rollback_receipt = enact.rollback(receipt.run_id)
print(rollback_result.decision) # "ROLLED_BACK"
print(rollback_result.actions_reversed) # ["revert_commit", "close_pr", "delete_branch"]
The rollback receipt looks like this — note the revert_sha showing exactly what was created on main:
{
"run_id": "rb-9f8e7d6c-...",
"original_run_id": "a1b2c3d4-...",
"workflow": "agent_pr_workflow",
"decision": "ROLLED_BACK",
"actions_reversed": [
{
"action": "revert_commit",
"system": "github",
"success": true,
"output": { "revert_sha": "f7c3a1b...", "reverted_merge": "e9d2c4a...", "base_branch": "main" }
},
{ "action": "close_pr", "system": "github", "success": true },
{ "action": "delete_branch", "system": "github", "success": true }
],
"timestamp": "2026-02-26T03:35:00Z",
"signature": "hmac-sha256-hex..."
}
One caveat on re-merging: A revert doesn't erase history. If you fix the issue and try to re-merge the same branch later, Git will skip those commits (it thinks they're already in main). You'd need to git revert <revert_sha> first — "undo the undo" — then merge. This is standard Git behavior, not an Enact quirk.
What if an action truly can't be undone? push_commit has no safe inverse without a force-push, which GitHub blocks on protected branches. If rollback hits one of these, it stops, records which action couldn't be reversed, and tells you exactly what to fix manually. It won't silently skip it.
Connectors & Allowed Actions
You might be thinking: "Don't we already have Policies?" Yes — but allowed_actions adds a complementary layer that works differently.
- Policies are your business rules: "You can push code, but not to the
masterbranch." allowed_actionsis your hardcoded floor: "This connector can only ever call these two methods. Full stop."
Policies handle the scenarios you anticipated. allowed_actions caps the blast radius for everything else — even actions you never thought to write a policy for. The list is checked before any API call, every time, with no exceptions.
Available Actions by Connector
| System | Actions | Rollback | Idempotent |
|---|---|---|---|
| GitHub | create_branch, create_pr, create_issue, delete_branch, merge_pr |
Yes — merge_pr via revert_commit; except push_commit |
Yes — already_done convention |
| Postgres | select_rows, insert_row, update_row, delete_row |
Yes — pre-SELECT captures state | Yes |
| Filesystem | read_file, write_file, delete_file, list_dir |
Yes — content captured before mutation | Yes |
| Slack | post_message, delete_message |
Yes — post_message via delete_message (bot token must have chat:delete scope) |
No — posting the same text twice is two messages, not a duplicate |
What Rollback Can and Can't Undo
| Action | Rollback? | How |
|---|---|---|
github.create_branch |
✅ | Deletes the branch |
github.create_pr |
✅ | Closes the PR |
github.merge_pr |
✅ | git revert -m 1 <sha> — adds a new commit to the base branch restoring pre-merge state. Safe on protected branches; no force-push. |
github.delete_branch |
✅ | Recreates branch at the captured SHA |
github.push_commit |
❌ | Un-pushing requires a destructive force-push, which GitHub blocks on protected branches |
postgres.insert_row |
✅ | Deletes the inserted row |
postgres.update_row |
✅ | Restores pre-update values (pre-SELECT captures state) |
postgres.delete_row |
✅ | Re-inserts every deleted row (pre-SELECT captures state) |
postgres.DROP TABLE |
❌ | Not a connector action — blocked by block_ddl policy. Even with captured rows, you'd lose indexes, constraints, sequences, and foreign keys. Prevention beats fake recovery. |
postgres.TRUNCATE |
❌ | Same as above — blocked by block_ddl |
filesystem.write_file |
✅ | Restores previous content (or deletes if file was new) |
filesystem.delete_file |
✅ | Recreates file with captured content |
slack.post_message |
✅ | Deletes the posted message via chat.delete using the ts timestamp captured at post time |
slack.delete_message |
❌ | You can't un-delete a Slack message |
One caveat on merge_pr rollback: After reverting a merge, if you fix the issue and try to re-merge the same branch, Git will skip those commits (they look already-merged). Revert the revert commit first (git revert <revert_sha>), then re-merge. This is standard Git behavior.
Built-in Policies
Enact ships 30 built-in policies across 9 categories so you don't have to write them from scratch:
| Category | Policies | What they block |
|---|---|---|
| Git | dont_push_to_main, require_branch_prefix, max_files_per_commit, dont_delete_branch, dont_merge_to_main |
Direct pushes to main, wrong branch names, blast radius |
| Database | dont_delete_row, dont_delete_without_where, dont_update_without_where, protect_tables, block_ddl |
Dangerous deletes, unscoped updates, DDL like DROP TABLE |
| Filesystem | dont_delete_file, restrict_paths, block_extensions |
File deletions, path traversal, sensitive files (.env, .key) |
| Access | contractor_cannot_write_pii, require_actor_role, require_user_role, dont_read_sensitive_tables, dont_read_sensitive_paths, require_clearance_for_path |
Unauthorized access, PII exposure |
| CRM | dont_duplicate_contacts, limit_tasks_per_contact |
Duplicate records, rate limiting |
| Time | within_maintenance_window, code_freeze_active |
Actions outside allowed hours, during code freezes |
| Slack | require_channel_allowlist, block_dms |
Off-list channel posts, direct messages to users |
no_mass_emails, no_repeat_emails |
Mass email blasts, spamming the same recipient | |
| Cloud | dont_delete_without_human_ok |
S3/GDrive deletions without cryptographic HITL approval |
from enact.policies.git import dont_push_to_main, require_branch_prefix
from enact.policies.db import protect_tables, block_ddl
from enact.policies.time import code_freeze_active
from enact.policies.slack import require_channel_allowlist, block_dms
from enact.policies.email import no_mass_emails
from enact.policies.cloud_storage import dont_delete_without_human_ok
Security
Receipts are HMAC-SHA256 signed. The signature covers every field — tampering with any field invalidates it.
export ENACT_SECRET="$(openssl rand -hex 32)"
Or pass secret= to EnactClient. Minimum 32 characters. No default.
For dev/testing only: EnactClient(..., secret="short", allow_insecure_secret=True)
Rollback verifies the receipt signature before executing any reversal — tampered receipts can't trigger unintended operations.
Cloud Features
Push receipts to the Enact cloud and use human-in-the-loop gates from any workflow.
from enact import EnactClient
enact = EnactClient(
systems={"github": gh},
policies=[dont_push_to_main],
workflows=[agent_pr_workflow],
secret="your-secret",
cloud_api_key="eak_...", # get at enact.cloud — enables cloud features
)
Push receipts to cloud storage:
result, receipt = enact.run(...)
enact.push_receipt_to_cloud(receipt) # receipt now searchable in cloud UI
Human-in-the-loop gate — pause a workflow and email a human to approve before continuing:
result, receipt = enact.run_with_hitl(
workflow="agent_pr_workflow",
user_email="agent@company.com",
payload={"repo": "myorg/app", "branch": "agent/nuke-main"},
notify_email="ops@company.com", # who gets the approve/deny email
timeout_seconds=3600, # auto-deny after 1 hour of silence
)
print(result.decision) # "PASS" if approved, "BLOCK" if denied or timed out
The approval email contains a signed link. Clicking approve or deny fires a callback and writes a HITL receipt. No credentials or login needed for the approver.
Status badge — embed in your README to show real-time pass/block rate for a workflow:

Run Tests
pytest tests/ -v
# 356+ tests, 0 failures (SDK + cloud)
Environment Variables
| Variable | Required | Purpose |
|---|---|---|
ENACT_SECRET |
Yes (or pass secret=) |
HMAC signing key. 32+ characters. |
GITHUB_TOKEN |
For GitHubConnector | GitHub PAT or App token |
SLACK_BOT_TOKEN |
For SlackConnector | Slack bot token (xoxb-...). Needs chat:write scope; add chat:delete to enable rollback. |
ENACT_FREEZE |
Optional | Set to 1 to activate code_freeze_active policy |
CLOUD_API_KEY |
For cloud features | API key from enact.cloud — enables receipt push + HITL |
CLOUD_SECRET |
Cloud backend only | Server-side signing secret for the cloud backend |
ENACT_EMAIL_DRY_RUN |
Cloud backend only | Set to 1 to skip real email sends in dev/test |
Deployment
The Enact landing page is hosted on Vercel with DNS managed via Porkbun.
- URL: https://enact.cloud
- Frontend: Static HTML (
index.html) - CI/CD: Auto-deploy on push to
masterbranch
Project details
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 enact_sdk-0.5.tar.gz.
File metadata
- Download URL: enact_sdk-0.5.tar.gz
- Upload date:
- Size: 105.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
022c5c77c8a642df3d33532bbce1a77ecd866489d32b2ad9a75840e24d35ce5e
|
|
| MD5 |
3495d90ed3af85a71b78eff61be77c16
|
|
| BLAKE2b-256 |
63dbbd3eecd388feb757b04e41ce2d4eae155a190e15bd05f4f2896542426fe7
|
File details
Details for the file enact_sdk-0.5-py3-none-any.whl.
File metadata
- Download URL: enact_sdk-0.5-py3-none-any.whl
- Upload date:
- Size: 71.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00ba6bef19aa9970776cadd6a90b84bf538e3d927cac4be611e7499d7d5196f2
|
|
| MD5 |
33bfccfc7395a2ca8ef2a51f836e3be0
|
|
| BLAKE2b-256 |
ef3b11131c1405d4864640844601b248159eeb1c0193ecbac019ceb0c491e41f
|