EnforceCore is a runtime enforcement layer for agentic AI systems. It intercepts tool/API calls at the code boundary and applies policy-driven rules — blocking denied calls, redacting PII, enforcing resource limits, and recording tamper-proof audit trails.
Prompt guardrails operate inside the LLM context — they ask the model to behave. EnforceCore operates at the runtime call boundary — the moment before a tool is actually executed. Prompts can be jailbroken; code-level enforcement cannot.
Yes. EnforceCore works with any Python-based agent system:
- Plain Python —
@enforce()decorator - LangGraph / LangChain —
@enforced_tool()adapter - CrewAI —
@enforced_tool()adapter - AutoGen —
@enforced_tool()adapter
No hard dependency on any framework. Adapters use optional imports.
Yes. EnforceCore v1.0.0 is stable and production-ready (pip install enforcecore).
It includes security hardening, unicode evasion protection, fail-closed
enforcement on all paths, and a 30-symbol public API frozen since beta.
1510 tests pass across Python 3.11/3.12/3.13. We still recommend thorough
testing with your own workloads, as with any security-critical dependency.
If anything goes wrong during enforcement (parsing error, internal
exception, etc.), the tool call is blocked — it never executes.
This is the safe default. The fail_open setting exists for development
only and emits a loud warning in production.
You can, but you should not. When fail_open=True, any internal
enforcement error causes the call to proceed unprotected. In production
this means a bug in EnforceCore could silently bypass all protection.
If you set fail_open=True without ENFORCECORE_DEV_MODE=1, you will
see a RuntimeWarning:
SECURITY WARNING: fail_open is enabled without ENFORCECORE_DEV_MODE=1.
An environment variable that acknowledges you are running in a development
environment. Set ENFORCECORE_DEV_MODE=1 to suppress the fail_open
security warning. Never set this in production.
Yes. Since v1.0.6, the PII redactor normalizes text before regex matching:
- NFC normalization and zero-width character stripping
- Homoglyph normalization (Cyrillic/Greek/fullwidth to ASCII)
- Encoded PII decoding (URL percent-encoding, HTML entities)
This defeats attacks like embedding PII with zero-width characters, using
Cyrillic look-alikes, or URL-encoding the @ sign in email addresses.
EnforceCore tracks nested enforcement calls via contextvars. If a tool
calls another enforced tool, the nesting depth increases. The default
maximum depth is 10. If exceeded, EnforcementDepthError is raised.
This prevents infinite recursion in enforcement chains.
| Category | Example | Placeholder |
|---|---|---|
email |
john@example.com |
<EMAIL> |
phone |
(555) 123-4567 |
<PHONE> |
ssn |
123-45-6789 |
<SSN> |
credit_card |
4111-1111-1111-1111 |
<CREDIT_CARD> |
ip_address |
192.168.1.100 |
<IP_ADDRESS> |
passport |
AB1234567 |
<PASSPORT> |
EnforceCore uses pure regex by default for zero heavy dependencies, portability, and speed (~0.03ms per call). Since v1.4.0, Presidio + spaCy are available as an optional NER backend for higher-accuracy detection:
pip install enforcecore[pii] # installs presidio-analyzer + spaCyfrom enforcecore import NERBackend, is_ner_available
if is_ner_available():
redactor = Redactor(ner_backend=NERBackend.PRESIDIO)Yes. Since v1.0.6, the deep_redact() function recursively traverses
dict, list, tuple, and set containers, applying redaction to all
string leaves. This is automatically used by enforce_sync() and
enforce_async().
| Strategy | Result for john@example.com |
|---|---|
placeholder |
<EMAIL> |
mask |
****@****.*** |
hash |
[SHA256:6b0b4806b1e57501] |
remove |
(empty string) |
| Component | Overhead |
|---|---|
| Policy evaluation | < 0.05ms |
| PII redaction (regex) | ~0.03ms |
| Audit entry | ~0.001ms |
| Resource guard | < 0.01ms |
| Typical total | ~0.09ms (full stack, p50) |
This is negligible compared to typical tool call latency (100ms–10s for API calls). Benchmarks measured on Python 3.14 with 20 scenarios.
Yes. Since v1.0.6, the policy cache uses a threading.Lock for
thread-safe access. Multiple threads can safely use @enforce() with
the same policy path.
By default, 10 MB (sum of all string and bytes arguments). This is
configurable via the max_bytes parameter of check_input_size().
Inputs exceeding this limit raise InputTooLargeError before any
processing occurs.
Each audit entry contains a previous_hash field pointing to the
SHA-256 hash of the preceding entry. This creates a linked chain where
any modification, deletion, insertion, or reordering is detectable by
verify_trail().
Yes. When you create an Auditor pointing to an existing trail file,
it reads the last entry's hash and continues the chain seamlessly.
Multiple sessions can append to the same trail file.
Yes. Since v1.0.6, load_trail() accepts a max_entries parameter.
When set, it returns only the most recent N entries from the trail:
recent = load_trail("audit.jsonl", max_entries=100)| Variable | Default | Description |
|---|---|---|
ENFORCECORE_DEFAULT_POLICY |
None |
Default policy file path |
ENFORCECORE_AUDIT_PATH |
./audit_logs/ |
Audit trail directory |
ENFORCECORE_AUDIT_ENABLED |
true |
Enable/disable auditing |
ENFORCECORE_REDACTION_ENABLED |
true |
Enable/disable PII redaction |
ENFORCECORE_LOG_LEVEL |
INFO |
Structured log level |
ENFORCECORE_COST_BUDGET_USD |
100.0 |
Global cost budget |
ENFORCECORE_FAIL_OPEN |
false |
Allow bypass on errors |
ENFORCECORE_DEV_MODE |
false |
Development mode flag |
Set ENFORCECORE_AUDIT_ENABLED=false in your test environment, or use
the _disable_audit_globally autouse fixture pattern from EnforceCore's
own test suite.