Hold back Production outbound side effects from flooding non-Production Frappe/ERPNext environments after a database restore.
Project description
cofferdam
Block outbound side effects — email, API calls, payments, webhooks — from non-production environments.
A cofferdam is a temporary watertight enclosure pumped dry so work can happen on a submerged foundation. This library is the software equivalent: it holds back outbound side effects from development, staging, and test environments so they cannot reach real customers, payment processors, or live vendor APIs.
The problem
When a non-production environment is refreshed from a production database backup, the restored data includes live email accounts, API credentials, payment configuration, webhook endpoints, and scheduled job settings. Without a guard, the non-production environment behaves like production — emailing customers, charging cards, and calling live APIs against real data.
How it works
cofferdam reads a local TOML policy file that lives on the environment's
filesystem — never in the database, never in a backup. That file is the
execution authority for every outbound action.
Restored data owns business intent. Local environment config owns execution authority.
The engine answers one question per outbound action:
Is this specific side effect permitted here?
It fails closed: absent, incomplete, or invalid policy denies the action.
Installation
$ pip install cofferdam # core library + CLI
$ pip install "cofferdam[http]" # adds the policy-checked HTTP helper (httpx)
Requires Python 3.10+.
Quick start
from cofferdam import load_policy
policy = load_policy("environment_policy.toml")
# Raise PolicyDeniedError if this side effect is not explicitly permitted here.
policy.assert_allowed(
integration="windmill",
kind="vendor_api",
operation="write",
method="POST",
host="sandbox.windmill.dev",
credential="windmill_default",
)
# Or inspect the decision without raising.
decision = policy.decide(integration="stripe", kind="payment", operation="capture")
if not decision.allowed:
print(decision.reason_code) # e.g. "operation_not_allowed"
Policy-checked HTTP — host validated against the policy before any bytes leave
(optional cofferdam[http] extra):
from cofferdam.http import request
response = request(
policy=policy,
integration="windmill",
operation="write",
method="POST",
url="https://sandbox.windmill.dev/api/jobs/run",
credential="windmill_default",
json=payload,
)
Email decoration
When an email is sent from a non-production environment, cofferdam decorates
the subject and body so recipients immediately know the source:
- Subject:
STAGING - Weekly Sales Report - Body: a warning banner (HTML) or a notice line (plain text) prepended to the original content
Decoration is on by default for any non-production environment and can be
disabled with decorate = false under [mail].
Example policy
environment = "staging"
default_decision = "deny"
[mail]
mode = "sink"
sink = "dev-inbox@example.internal" # all outbound mail redirected here
[integrations.windmill]
enabled = true
kind = "vendor_api"
credential = "windmill_default"
allowed_hosts = ["sandbox.windmill.dev"]
allowed_methods = ["GET", "POST"]
allowed_operations = ["read", "write"]
[credentials.windmill_default]
profile = "staging"
secret_env = "WINDMILL_API_KEY"
[integrations.stripe]
enabled = true
kind = "payment"
credential = "stripe_default"
allowed_hosts = ["api.stripe.com"]
allow_authorize = true
allow_capture = false # never capture money outside Production
[credentials.stripe_default]
profile = "sandbox"
secret_env = "STRIPE_SANDBOX_SECRET_KEY"
[effects.email.customer]
enabled = false # no customer email in Staging
[effects.email.internal]
enabled = true
allow_domains = ["example.internal"]
CLI
$ cofferdam validate path/to/environment_policy.toml
$ cofferdam inspect path/to/environment_policy.toml
$ cofferdam decide path/to/environment_policy.toml \
--integration windmill --kind vendor_api --operation write \
--method POST --url https://sandbox.windmill.dev/api/jobs/run \
--credential windmill_default
validate exits non-zero on invalid policy. inspect prints a redacted
summary — secrets are never shown. decide prints allow/deny and a stable
reason code.
Frappe / ERPNext
cofferdam was designed with ERPNext and Frappe Framework in mind — the
database restore pattern is a daily reality for that ecosystem. The core
library has no dependency on Frappe and works with any Python application.
For full Frappe/ERPNext integration — automatic policy discovery by bench
site, interception of frappe.sendmail, and webhook delivery gating — see
cofferdam-app, a companion
Frappe app that installs into a bench alongside your ERPNext site.
Requirements specification
This library is built with a document-driven workflow. See docs/
for the full requirements specification and docs/adr/ for
architecture decisions.
License
Apache-2.0 © 2026 Brian Pond / Datahenge LLC
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 cofferdam-0.1.0.tar.gz.
File metadata
- Download URL: cofferdam-0.1.0.tar.gz
- Upload date:
- Size: 50.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5667ce0371d82f66c6fd340eb545986d4d96f8bf8b57f36082754fee89093eea
|
|
| MD5 |
434fdd79a3e065ec4fd14c7502640837
|
|
| BLAKE2b-256 |
a4f0c2fb10061b3572d3f5c9bca6bc161574a8f296c8e8021f5308d49f2da836
|
File details
Details for the file cofferdam-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cofferdam-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bace5010f690496f5796c2bc91cbb6b4ebef63abad90e0a9dd38ed72bcf6f8bc
|
|
| MD5 |
76010d65490c28ff06f3a41e578db092
|
|
| BLAKE2b-256 |
e859c269f913215938ec8a99f6653b19f2d924530ffa0a674f9ec59b526b5b0a
|