Troubleshooting & FAQ
Common Errors
InvalidToolNameError: Tool name contains invalid characters
The tool name contains characters outside the allowed set: [a-zA-Z0-9_.\-:<>].
Fix: Pass a valid tool_name explicitly:
enforcer.enforce_sync(my_func, arg, tool_name="my_tool")
InputTooLargeError: Input size exceeds limit
Combined string/bytes arguments exceed 10 MB (default).
Fix: Reduce input size, or increase the limit:
from enforcecore import check_input_size
check_input_size(args, kwargs, max_bytes=50 * 1024 * 1024) # 50 MB
EnforcementDepthError: Nesting depth exceeds maximum
An enforced tool called another enforced tool, exceeding the max depth of 10.
Fix: Check for recursive chains. The error includes the call chain for debugging.
ToolDeniedError: Tool not in allowed list
The policy does not allow the tool being called.
Fix: Add the tool name to allowed_tools in your policy:
rules:
allowed_tools:
- "your_tool_name" # Add this
CostLimitError: Cumulative cost exceeds budget
Total recorded cost exceeded the budget in resource_limits.max_cost_usd.
Fix: Increase the budget, or reset the tracker:
enforcer.guard.cost_tracker.reset()
ResourceLimitError: Call duration exceeded
Tool call exceeded resource_limits.max_call_duration_seconds.
Fix: Increase the time limit or optimize the tool.
PolicyLoadError: No policy provided
@enforce was used without a policy and no default is configured.
Fix: Pass a policy, or set an env var:
ENFORCECORE_DEFAULT_POLICY=policies/default.yaml
RateLimitError: Rate limit exceeded
Too many calls in the configured time window — either global RPM or per-tool limit.
Fix: Increase the rate limit in your policy:
rules:
rate_limits:
global_rpm: 120 # Increase from default
per_tool:
search_web: 60 # Per-tool override
ContentViolationError: Content rule violated
Tool arguments or output matched a dangerous content pattern (shell injection, path traversal, SQL injection, or code execution).
Fix: Review the flagged content. If it's a false positive, you can disable specific rule categories:
rules:
content_rules:
enabled: true
categories: [shell_injection, sql_injection] # Only these two
DomainDeniedError: Domain not allowed
The tool attempted to access a domain not in the allow list or explicitly in the deny list.
Fix: Add the domain to your policy's network allow list:
rules:
network:
allowed_domains: ["api.example.com", "cdn.example.com"]
PolicyEvaluationError: Error during rule evaluation
A rule in the policy failed to evaluate — often due to malformed conditions or missing fields.
Fix: Validate your policy with the CLI:
enforcecore validate --policy policy.yaml
RuntimeWarning: fail_open is enabled without ENFORCECORE_DEV_MODE=1
fail_open=True is set but you're not in dev mode.
Fix: Either disable fail_open (recommended) or acknowledge dev mode:
ENFORCECORE_FAIL_OPEN=false # Production
ENFORCECORE_DEV_MODE=1 # Development only
Debugging Tips
Enable debug logging
ENFORCECORE_LOG_LEVEL=DEBUG
Shows every enforcement decision, redaction event, and audit entry.
Check enforcement scope
from enforcecore import get_enforcement_depth, get_enforcement_chain
print(f"Depth: {get_enforcement_depth()}")
print(f"Chain: {get_enforcement_chain()}")
Verify audit trail integrity
from enforcecore import verify_trail
result = verify_trail("audit.jsonl")
if not result.is_valid:
for error in result.errors:
print(f" ERROR: {error}")
Test your policy
from enforcecore.eval import ScenarioRunner
from enforcecore.core.policy import Policy
policy = Policy.from_file("policy.yaml")
runner = ScenarioRunner(policy)
suite = runner.run_all()
print(f"Containment: {suite.containment_rate:.0%}")
Use the CLI
EnforceCore includes 6 CLI commands for debugging and inspection:
# Show installed version and configuration
enforcecore info
# Validate a policy file
enforcecore validate --policy policy.yaml
# Verify an audit trail
enforcecore verify --trail audit.jsonl
# Run evaluation scenarios
enforcecore eval --scenarios all
# Dry-run a policy without executing tools
enforcecore dry-run --policy policy.yaml --scenarios all
# Inspect effective policy rules
enforcecore inspect --policy policy.yaml
FAQ
What is EnforceCore?
A runtime enforcement layer for AI agents. See the overview page for the full introduction.
How is this different from prompt guardrails?
Prompt guardrails ask the model to behave — they can be jailbroken. EnforceCore operates at the runtime call boundary — code-level enforcement that cannot be bypassed. See Why Not Prompt-Level Guardrails? on the overview page.
Does EnforceCore work with my framework?
Yes — any Python agent system. See the Integrations page for framework-specific adapters.
What does "fail-closed" mean?
If anything goes wrong during enforcement, the call is blocked — never allowed through. This is the safe default.
Does it protect against unicode PII evasion?
Yes. Since v1.0.6: NFC normalization, zero-width character stripping, homoglyph detection (~40 confusables), and URL/HTML entity decoding. All automatic before PII pattern matching.
What PII categories are supported?
6 categories: email, phone, SSN, credit card, IP address, passport. 4 redaction strategies: placeholder, mask, hash, remove. See the API Reference for the full table.
Why regex instead of spaCy/Presidio?
Presidio requires spaCy + Pydantic v1, incompatible with Python 3.14. Regex is zero-dep, portable, and ~0.1–0.5ms per call.
What's the enforcement overhead?
Full E2E: 0.056 ms P50, 0.892 ms P99. Negligible compared to tool call latency (100ms–10s). See the Evaluation Suite for all 15 per-component benchmarks.
What secrets does EnforceCore detect?
The built-in secret scanner covers 11 categories: AWS access keys, AWS secret keys, GitHub tokens, Google Cloud API keys, Azure connection strings, database URIs, SSH private keys, generic API keys, JWT tokens, Slack tokens, and Stripe keys. You can add custom patterns via PatternRegistry.
What are content rules?
4 built-in pattern categories (shell injection, path traversal, SQL injection, code execution) that fire before the tool runs. See the API Reference for details.
How do lifecycle hooks work?
Four hook points: @on_pre_call, @on_post_call, @on_violation, @on_redaction. See the API Reference for usage examples.
What happened to guard_sync() and guard_async()?
Removed in v1.0.16a1. Use Enforcer.from_file() factory or @enforce() decorator instead. See the API Reference for migration details.
What is the Tier 1 / Tier 2 API split?
As of v1.0.0b1, EnforceCore has 30 Tier 1 symbols — the stable, frozen public contract. The remaining 80 Tier 2 symbols are still importable but are not covered by the stability guarantee. Tier 2 imports emit deprecation warnings. If you only use Tier 1 symbols, your code is guaranteed forward-compatible through the v1.x series. See the API Reference for the full Tier 1 list.
Is EnforceCore stable enough for production?
Yes. EnforceCore is stable (v1.0.1) with 1,510 tests, 95% coverage, 22 formal invariants, and a frozen 30-symbol API.
What changed in the security audit (v1.0.24a1)?
Five targeted fixes: (A-4) AuditEntry JSON-safety validation, (M-4) Shannon entropy filter for secret detection false-positive reduction, (A-5) minimum input size floor, (M-5) Unicode normalization rewrite with correct offset mapping, (M-2) Enforcer internal refactoring. See the Roadmap for details.
What is Policy.from_dict() auto-hoisting (v1.0.0b2)?
A bug fix. Previously, flat dictionary keys passed to Policy.from_dict() were silently dropped. Now, if a top-level key matches a known rule field (like allowed_tools), it is auto-hoisted into the rules: namespace. This prevents silent policy misconfiguration.
How does the Merkle chain work?
Each audit entry contains the SHA-256 hash of the preceding entry. Any tampering is detectable by verify_trail(). Cross-session chain continuity is automatic. See Architecture — Merkle Auditor for details.
What is append-only immutable mode?
Since v1.0.0b4, Auditor(immutable=True) sets the OS-level append-only attribute on the audit file (chattr +a on Linux, chflags uappend on macOS). This prevents truncation, overwriting, or chain rebuild — even by the file owner. In Docker, the container needs --cap-add LINUX_IMMUTABLE. Enable via env var: ENFORCECORE_AUDIT_IMMUTABLE=true.
What are witness backends?
Witness backends (v1.0.0b4) publish entry hashes (~200 bytes each) to a separate location — CallbackWitness (queues, HTTP, databases), FileWitness (separate JSONL), or LogWitness (syslog/journald). Use verify_with_witness() to cross-check trail hashes against witness records, detecting chain-rebuild attacks that verify_trail() alone cannot. Enable via env var: ENFORCECORE_AUDIT_WITNESS_FILE=/path/to/witness.jsonl.
Can I combine witness and immutable?
Yes — and you should for maximum tamper-evidence. Immutable prevents local file manipulation; witness provides an independent second record. Use both:
ENFORCECORE_AUDIT_IMMUTABLE=true
ENFORCECORE_AUDIT_WITNESS_FILE=/secure/witness.jsonl
Who contributed to the tamper-evidence design?
Prof. Dan S. Wallach (Rice University) provided direct guidance on the append-only + witness mitigations. He is acknowledged in CONTRIBUTORS.md under Design Feedback.
Quick References
- Platform support (Resource Guard): See Architecture — Resource Guard
- All 19 environment variables: See API Reference — Environment Variables
- Full benchmarks: See Evaluation Suite