Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cofferdam-0.1.0.tar.gz (50.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cofferdam-0.1.0-py3-none-any.whl (24.4 kB view details)

Uploaded Python 3

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

Hashes for cofferdam-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5667ce0371d82f66c6fd340eb545986d4d96f8bf8b57f36082754fee89093eea
MD5 434fdd79a3e065ec4fd14c7502640837
BLAKE2b-256 a4f0c2fb10061b3572d3f5c9bca6bc161574a8f296c8e8021f5308d49f2da836

See more details on using hashes here.

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

Hashes for cofferdam-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bace5010f690496f5796c2bc91cbb6b4ebef63abad90e0a9dd38ed72bcf6f8bc
MD5 76010d65490c28ff06f3a41e578db092
BLAKE2b-256 e859c269f913215938ec8a99f6653b19f2d924530ffa0a674f9ec59b526b5b0a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page