Record the insecure-coding MUST/MUST NOT rules and point new code at existing CrewAI helpers instead of reimplementing the same controls. Co-authored-by: matt <matt@crewai.com>
14 KiB
Secure Coding Standard
This document is authoritative for all AI coding agents, automated code reviewers, and human contributors. Any conflict between generated code and this standard MUST be resolved in favor of this standard.
This document defines the minimum secure coding requirements for all CrewAI open-source repositories.
Its goals are to:
- Produce secure-by-default code.
- Guide AI coding agents during implementation.
- Provide objective criteria for security code reviews.
- Reduce false-positive vulnerability reports by defining the project's expected security controls.
All requirements in this document are normative.
The keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are interpreted as described in RFC 2119.
Scope
This document specifies insecure-coding requirements: injection, path traversal, unsafe deserialization, secret exposure, unbounded I/O, and similar implementation defects.
It does not specify model-level or product-threat controls. Prompt
injection, tool-metadata influence, and similar AI risks belong in
docs/security/threat-model.md, not here.
Security Principles
All code MUST follow these principles.
Validate Untrusted Input
Assume all external input is untrusted until validated.
Examples include:
- User input
- HTTP requests
- Environment variables
- Files
- Archives
- LLM responses
- Tool outputs
- Plugin outputs
- Network responses
Least Privilege
Components MUST only receive the permissions they require.
Avoid unnecessary:
- filesystem access
- network access
- process execution
- credentials
- secrets
Fail Securely
Security-sensitive operations MUST fail closed.
Do not continue after:
- failed authorization
- failed validation
- partial execution
- partial transactions
Roll back where appropriate.
Defense in Depth
Security MUST NOT rely on a single validation step.
Where appropriate:
- validate input
- authorize operations
- restrict capabilities
- log security events
- enforce resource limits
Approved Project Abstractions
AI-generated and human-authored code MUST use these helpers instead of new implementations of the same control.
| Control | MUST use |
|---|---|
| File and directory containment | crewai_tools.security.safe_path.validate_file_path, validate_directory_path |
| Path and error display | format_path_for_display, format_error_for_display, format_sandbox_error |
| URL validation | crewai_tools.security.safe_path.validate_url |
| HTTP fetch | crewai_tools.security.safe_requests.safe_get, safe_get_bounded |
| Archive extraction | tarfile.extractall(..., filter="data") when available; otherwise the _safe_extractall / _safe_extract_zip pattern in crewai.skills.cache and crewai_cli.skills.main |
| YAML | yaml.safe_load() |
| Project Python references | existing crew-loader confinement to the project root; MUST NOT add a parallel importer |
| SQL values | parameterized queries / bind parameters |
| SQL statement policy in NL2SQL-style tools | the existing default-deny DML / multi-statement checks in NL2SQLTool |
crewai_tools.utilities.safe_path is a compatibility re-export. New code MUST
import from crewai_tools.security.safe_path.
Calling validate_url() and then requests.get() is not sufficient. DNS
rebinding and redirect hops require safe_get / safe_get_bounded.
CREWAI_TOOLS_ALLOW_UNSAFE_PATHS disables both path and SSRF checks
process-wide. New code MUST NOT add another process-wide escape hatch. Prefer
base_dir to widen a sandbox. Managed workers MUST keep
CREWAI_TOOLS_FORCE_SAFE_PATHS as the override that cannot be disabled by
ALLOW_UNSAFE_PATHS.
Existing uses of prohibited APIs in this repository do not authorize copying those APIs into new code.
1. Command Execution
Risk
Unsafe process execution may result in command injection or arbitrary command execution.
MUST NOT
os.system()os.popen()subprocess.run(..., shell=True)subprocess.Popen(..., shell=True)subprocess.call(..., shell=True)subprocess.check_call(..., shell=True)subprocess.check_output(..., shell=True)
MUST NOT construct shell commands using:
- string concatenation
- f-strings
%formatting.format()
MUST
Use:
subprocess.run([...], shell=False, check=True)
Pass arguments as a list.
Validate all externally influenced arguments.
The executable and each argument that is not a literal or allowlisted constant
MUST be validated before use. Hardcoded optional-dependency installs such as
subprocess.run(["uv", "add", "package"], shell=False, check=True) are
acceptable only when no argument is derived from untrusted input.
2. Filesystem Access
Risk
Unsafe filesystem operations may allow:
- path traversal
- symlink attacks
- arbitrary file overwrite
- unauthorized file access
MUST NOT
- Trust user-provided paths.
- Validate paths using
startswith(). - Extract archives without validating every entry.
MUST
- Canonicalize paths.
- Validate against an approved root directory.
- Reject paths escaping the allowed root.
- Validate archive contents before extraction.
In crewai-tools, MUST call validate_file_path or
validate_directory_path. MUST NOT reimplement containment with startswith,
string prefix checks, or manual .. filtering. The helpers resolve symlinks
and .. before comparing against base_dir.
Archive members MUST be validated before extraction, including symlink and
hardlink targets, not only member names. Copy the existing _safe_extractall
/ _safe_extract_zip checks; do not add extractall without them.
3. Dynamic Code Execution
Risk
Executing attacker-controlled code results in arbitrary code execution.
MUST NOT
eval()exec()compile()on untrusted input- Dynamic imports from user-controlled values
MUST
Use:
- explicit parsing
- JSON
- Pydantic models
- controlled plugin registries
- allowlists
ast.literal_eval MAY be used to parse literals. It is not a substitute for
json.loads when the input is JSON.
Project definition Python references MUST resolve through the existing loader,
which confines imports to the project root and rejects stdlib/external targets
such as os.system. New code MUST NOT call importlib.import_module on a
value derived from untrusted input.
4. Deserialization
Risk
Unsafe deserialization can execute arbitrary code.
MUST NOT
pickle.load()pickle.loads()dill.load()dill.loads()marshal.loads()yaml.load()with unsafe loaders
MUST
Use:
json.loads()yaml.safe_load()- schema validation
- Pydantic models
Do not add new pickle, dill, or marshal loaders. Existing pickle usage is not a template for new code.
5. Injection
Risk
User-controlled data MUST NOT become executable instructions.
Includes:
- SQL Injection
- Command Injection
- Template Injection
- LDAP Injection
- XPath Injection
- NoSQL Injection
- Log Injection
MUST NOT
- Build SQL using string formatting.
- Build commands using string concatenation.
- Insert unescaped user input into templates.
MUST
Use:
- parameterized queries
- prepared statements
- structured logging
- output encoding
SQL values MUST be passed as bind parameters (for example SQLAlchemy
text(query) with a params mapping). MUST NOT interpolate untrusted strings
into the query text.
Tools that accept whole SQL statements MUST keep a default-deny write policy
and reject stacked statements in read-only mode, following NL2SQLTool. MUST
NOT add a new SQL tool that executes caller-supplied SQL with no statement
policy.
6. Network Requests
Risk
Network requests can expose internal services or leak sensitive information.
MUST NOT
- Disable TLS verification.
- Fetch arbitrary URLs without validation.
- Follow redirects blindly.
- Make requests without timeouts.
MUST
- Validate URLs.
- Restrict supported protocols.
- Block localhost and private addresses where appropriate.
- Configure connection and read timeouts.
- Verify TLS certificates.
In crewai-tools and any code that fetches URLs chosen at runtime, MUST use
safe_get or safe_get_bounded. Those helpers:
- allow only
http/https - block
file:// - reject private, loopback, link-local, and metadata addresses
- validate every redirect hop
- pin TCP to the checked IP
- strip credentials on cross-origin redirects
- default timeout to 30 seconds
safe_get_bounded MUST be used when the response body is read into memory.
MUST NOT pass verify=False. MUST NOT follow redirects with a raw
requests/httpx client.
7. Secrets
Risk
Hardcoded or exposed secrets compromise deployments.
MUST NOT
- Hardcode API keys.
- Hardcode passwords.
- Hardcode tokens.
- Hardcode private keys.
- Log secrets.
- Return secrets in exceptions.
- Commit credentials to source control.
MUST
Use:
- environment variables
- secret management systems
- secret masking
- dependency injection
Logs, metrics, traces, and exceptions MUST NOT contain secrets or absolute
filesystem prefixes. Use format_path_for_display and
format_error_for_display instead of interpolating raw paths or OSError
text.
8. Authorization
Risk
Every sensitive operation requires authorization.
MUST NOT
- Trust user-supplied roles.
- Trust user-supplied ownership.
- Skip authorization checks.
- Expand tool capabilities beyond their documented purpose.
MUST
- Verify authorization before sensitive operations.
- Enforce least privilege.
- Validate ownership server-side.
- Respect documented trust boundaries.
9. Resource Management
Risk
Unbounded operations may result in denial of service.
MUST NOT
- Read unbounded files.
- Create infinite loops.
- Allow unlimited retries.
- Spawn unlimited threads.
- Process unlimited input.
- Execute operations without timeouts.
MUST
- Limit file sizes.
- Limit memory usage.
- Configure execution timeouts.
- Configure network timeouts.
- Limit concurrency.
- Validate input sizes.
HTTP response bodies MUST be capped with safe_get_bounded. File and archive
readers MUST enforce a size limit before loading content into memory. Network
and subprocess calls MUST set a timeout.
In crewai-files, MUST reuse the existing provider constraint validators
rather than adding unbounded reads.
10. Cryptography
Risk
Weak cryptography compromises confidentiality and integrity.
MUST NOT
- Use
randomfor security-sensitive values. - Invent cryptographic algorithms.
- Store plaintext passwords.
- Compare secrets using normal equality.
- Disable certificate validation.
MUST
Use:
secretshmac.compare_digest()- Argon2id, bcrypt, or scrypt
- authenticated encryption
- modern TLS
Error Handling
Security-relevant exceptions MUST be handled intentionally.
MUST NOT
- Swallow exceptions.
- Continue after partial failure.
- Expose stack traces to users.
- Leak filesystem paths or secrets.
MUST
- Fail securely.
- Roll back incomplete operations.
- Return safe error messages.
- Log security-relevant failures.
User-facing tool errors MUST go through format_sandbox_error /
format_error_for_display so they do not advertise
CREWAI_TOOLS_ALLOW_UNSAFE_PATHS or leak absolute paths.
Security Logging
The following SHOULD generate security logs where applicable:
- authentication attempts
- authorization failures
- input validation failures
- rejected commands
- rejected filesystem access
- security policy violations
- privilege changes
- security configuration changes
Logs MUST NOT contain:
- passwords
- API keys
- tokens
- private keys
- sensitive personal data
- absolute filesystem prefixes of rejected paths
AI Code Generation
AI-generated code MUST be treated as untrusted until reviewed.
Before completing an implementation, AI coding agents MUST:
- Review generated code against this document.
- Remove prohibited APIs and patterns.
- Verify that security controls have not been removed.
- Prefer approved project abstractions over new implementations.
- State any security assumptions made.
AI MUST NOT remove or weaken existing:
- validation
- authorization
- error handling
- logging
- resource limits
without explicit justification.
New code MUST NOT introduce APIs listed as MUST NOT, including when similar calls already exist elsewhere in the tree.
AI Security Reviews
When reviewing existing code, findings MUST distinguish between:
- Security vulnerabilities
- Hardening recommendations
- Code quality issues
The existence of a dangerous API does not, by itself, establish a security vulnerability.
A valid finding MUST demonstrate:
- Attacker-controlled input.
- Reachable source-to-sink data flow.
- A security-sensitive operation.
- Failure of an intended security control.
- Demonstrable security impact.
Reports MUST follow the assumptions defined in
docs/security/threat-model.md.
This split is intentional:
- Implementation follows the MUST / MUST NOT rules so new code is secure-by-default.
- Vulnerability reports require the five-point test so existing or gated use of a dangerous API is not filed as a CVE without a reachable exploit.
Pull Request Checklist
Before merging, verify:
- No prohibited APIs introduced.
- Approved project abstractions used instead of new security helpers.
- Inputs are validated.
- Authorization is enforced.
- No unsafe deserialization.
- No injection vulnerabilities.
- Network requests are bounded and validated.
- Secrets are not exposed.
- Resource limits exist.
- Errors fail securely.
- Security logging is preserved.
- AI-generated code has been reviewed against this document.