Skip to main content
This guide provides instructions for hardening your QWED deployment for production environments. It covers prompt injection defense, secret management, network security, authentication, and compliance with OWASP LLM Top 10.
Only QWED v5.x is currently supported with security patches and updates. Versions 4.x and earlier are end-of-life. If you are running a prior version, upgrade before applying the guidance below.

1. Secret Management

QWED relies on environment variables for sensitive configuration. Never commit .env files to version control.

Environment Variable Injection

For production deployments, we recommend injecting environment variables using your infrastructure’s secret management solution.

Docker / Kubernetes

Use Kubernetes Secrets or HashiCorp Vault to inject secrets as environment variables into the container.
# Example Kubernetes Pod Spec
env:
  - name: OPENAI_API_KEY
    valueFrom:
      secretKeyRef:
        name: qwed-secrets
        key: openai-api-key
  - name: JWT_SECRET_KEY
    valueFrom:
      secretKeyRef:
        name: qwed-secrets
        key: jwt-secret

HashiCorp Vault Integration

If you use Vault, you can use envconsul or the Vault Agent Injector to populate environment variables before the application starts.
# Example with envconsul
envconsul -prefix qwed/prod/ qwed-api

Critical secrets

Required secrets

Changed in v5.0.0
API_KEY_SECRET is now mandatory — the server will not start without it. This secret is used for PBKDF2 API-key hashing. Generate one with:
python -c "import secrets; print(secrets.token_urlsafe(48))"
Ensure these secrets are rotated regularly:
  • API_KEY_SECRET (used for API key hashing — required)
  • OPENAI_API_KEY (and other provider keys)
  • API_KEY_SECRET (used for signing JWTs — mandatory, the server will not start without it)
  • JWT_SECRET_KEY (used for signing session tokens)
  • DATABASE_URL (if using an external database)
As of v5.0.0, API_KEY_SECRET no longer has a default value. You must set it explicitly before starting the server. Generate a secure value with:
python -c "import secrets; print(secrets.token_urlsafe(48))"

2. Network Security

Firewall Rules

Restrict network access to the QWED API server:
  • Public Access: Allow ports 80 / 443 only via a Load Balancer / WAF.
  • Internal Access: The QWED application port (default 8000) should not be directly exposed to the internet.
  • Database: Block all public access to the database port (e.g., 5432). Only allow connections from the QWED application subnet.

Rate limiting (production)

QWED includes a default in-memory rate limiter that is thread-safe — all check and record operations are protected by a lock, so it is safe to use with multi-threaded ASGI servers. For production, you should tune these limits and consider a distributed solution.

Configuration

You can adjust the rate limits using environment variables:
Environment VariableDefaultDescription
RATE_LIMIT_REQUESTS_PER_MINUTE100Max requests per minute per API key.
RATE_LIMIT_WINDOW_SECONDS60Time window in seconds.
Example:
export RATE_LIMIT_REQUESTS_PER_MINUTE=50
export RATE_LIMIT_WINDOW_SECONDS=60

Thread-safe in-memory limiter

New in v5.0.0
The in-memory rate limiter is now thread-safe. All check-and-record operations are protected by a lock, which prevents race conditions when multiple threads handle concurrent requests in a single process. This eliminates a class of bypass where two requests arriving simultaneously could both pass the limit check before either was recorded. The default in-memory limiter does not scale across multiple worker processes or replicas. For high-availability deployments, we recommend using a Redis-backed rate limiter to ensure consistent enforcement across your cluster.
The Redis-backed rate limiter uses a fail-closed policy. If Redis becomes unreachable at runtime, requests are denied rather than allowed through. This prevents a Redis outage from silently disabling rate limiting. An in-memory fallback is only used when Redis is unavailable at initialization time.

CORS configuration

QWED requires you to explicitly configure allowed CORS origins. The QWED_CORS_ORIGINS environment variable must be set to a comma-separated list of trusted domains — the server will refuse to start if this variable is empty or unset.
Changed in v5.0.0
QWED no longer defaults to * for CORS origins. The QWED_CORS_ORIGINS environment variable is required — the server will refuse to start if it is not set. Set it to a comma-separated list of trusted origins:
export QWED_CORS_ORIGINS="https://app.yourcompany.com,https://admin.yourcompany.com"
Previous versions defaulted to allowing all origins (*) for development convenience. As of v5.0.0, CORS origins must be explicitly configured. If you need to allow all origins during local development, set QWED_CORS_ORIGINS="*" — but never use this in production.
When the origin list is set to *, the allow_credentials CORS header is automatically set to false to prevent credential leakage. For specific origin lists, credentials are allowed. If you were previously relying on the default * origin, you must now explicitly set QWED_CORS_ORIGINS before upgrading. This is a breaking change in v5.0.0. When QWED_CORS_ORIGINS is set to a wildcard (*), credentialed requests (allow_credentials) are automatically disabled to comply with the CORS specification.

3. Authentication Hardening

API key rotation

Regularly rotate API keys to minimize the impact of a potential leak. QWED provides a built-in rotation mechanism via the POST /admin/keys/rotate endpoint. Old keys should be revoked immediately after the rotation window.

Password policies

If you integrate QWED with a custom user database:
  • Enforce a minimum length of 12 characters.
  • Require a mix of uppercase, lowercase, numbers, and special characters.
  • Use the built-in bcrypt hashing provided by QWED’s authentication module.

Session Management

Control the lifetime of access tokens to reduce the window of opportunity for session hijacking.
Environment VariableDefaultDescription
JWT_ACCESS_TOKEN_EXPIRE_MINUTES60Minutes until the access token expires.
Recommendation: Set this to a lower value (e.g., 15 or 30 minutes) and implement refresh tokens if needed.
export JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30

4. Enhanced prompt injection defense

New in v4.0.0
QWED v4.0.0 introduces a multi-layer EnhancedSecurityGateway (OWASP LLM01:2025 compliant) that screens all inputs through seven defense layers:
LayerDefenseDescription
1Pattern detectionHeuristic matching against 14 known injection patterns
2Length limitingStrict 2,000-character limit (blocks ~70% of injection attacks)
3Base64 decodingDetects and decodes base64-encoded payloads, then scans for injection keywords
4Semantic similarityUses sequence matching against system prompt (threshold: 0.6)
5Keyword detection28 advanced keywords: disregard, override, bypass, jailbreak, etc.
6Unicode script mixingDetects Cyrillic/Arabic/Greek characters mixed with Latin (homoglyph attacks)
7Zero-width charactersDetects invisible characters (\u200B, \u200C, \u200D, \uFEFF) used to hide payloads
The gateway is enabled by default for all API endpoints. It also includes a block counter for monitoring injection attempt frequency.

PII redaction

All API error paths now use a centralized redact_pii() function that masks email addresses, phone numbers, SSNs, and IP addresses before they reach logs. Stack traces are suppressed in error responses (exc_info=False) to prevent data leakage.

Fail-closed code execution

New in v4.0.4
All model-generated Python code — in both the Stats and Consensus engines — runs exclusively inside a Docker sandbox (SecureCodeExecutor). The in-process Wasm and restricted execution fallbacks that existed in earlier versions have been permanently disabled. If the Docker daemon is unreachable, the affected endpoints return HTTP 503 rather than degrading to an insecure execution mode. The executor performs a live health check (docker.ping()) on every request to detect runtime failures that would not be caught by a startup-time flag. This design ensures that a Docker outage is surfaced as a visible service disruption rather than silently downgrading security.

Legacy CodeExecutor hard-blocked

The legacy CodeExecutor class — which previously used raw exec() to run model-generated Python — is now permanently hard-blocked. Any call to CodeExecutor.execute() raises a RuntimeError directing you to use SecureCodeExecutor instead. If you import CodeExecutor directly in custom code or tests, you must migrate to SecureCodeExecutor:
# Before (raises RuntimeError)
from qwed_new.core.code_executor import CodeExecutor
executor = CodeExecutor()
executor.execute(code)  # RuntimeError

# After
from qwed_new.core.secure_code_executor import SecureCodeExecutor
executor = SecureCodeExecutor()
result = executor.execute(code)
This change has no effect on the public API — the /verify/stats and /verify/consensus endpoints already use SecureCodeExecutor since v4.0.4. It only affects self-hosted deployments that imported CodeExecutor directly.

Default-deny for unknown tool approvals

New in v5.1.0
The tool approval system now blocks all unknown tools by default, regardless of their heuristic risk score. In previous versions, tools with a risk score below 0.3 were automatically approved even if they were not in the allowlist. As of v5.1.0, any tool that is not explicitly allowlisted is denied unconditionally. If you manage a custom tool allowlist, ensure all tools your agents use are explicitly registered. Unregistered tools return a blocked response with the message "Unknown tool '<name>' requires explicit allowlisting".

Safe expression evaluation

All eval() calls have been fully eliminated and replaced with a custom AST-walking evaluator. Instead of compiling and executing code, the evaluator parses expressions into an AST, validates every node against a strict allow-list, and then interprets the tree directly — no eval() or compile() is ever invoked. The safe evaluator enforces three layers of defense:
  1. AST allow-list — only permitted node types (Constant, Name, BinOp, UnaryOp, Call, Tuple, List, and approved operators) pass validation. Unknown or dangerous nodes are rejected before evaluation.
  2. CodeGuard integration — when the full qwed_new package is available, expressions are additionally screened by CodeGuard before execution.
  3. Restricted namespace — the evaluator resolves symbols only from an explicit namespace. For SymPy paths, only whitelisted functions (e.g., sqrt, sin, cos, log, Rational, pi, E) are available. For Z3 paths, only And, Or, Not, Implies, If, Int, Bool, and Real are permitted.
Additional safeguards:
  • Keyword unpacking (**kwargs via None-keyed keywords) is blocked in all call nodes
  • __ (double underscore) patterns are blocked to prevent attribute access attacks
  • SymPy expressions use exact arithmetic (sympy.Integer, sympy.Float) to avoid floating-point drift during comparison

Credential store security

The qwed init wizard and YAML provider config system write .env files with strict security guarantees:
  • .env files written with 0600 permissions (owner-only on Unix)
  • Atomic writes via tempfile + os.replace to prevent partial writes
  • Symlink attack prevention (O_NOFOLLOW on Unix)
  • Automatic .gitignore verification ensures .env is excluded from version control
  • API keys are never printed in full — only the first 8 characters are shown in logs

5. OWASP LLM Top 10 compliance

QWED implements technical defenses for the OWASP LLM Top 10. However, security is a shared responsibility.

Your Responsibilities (User/Deployment)

While QWED handles internal verification, you must implement the following “Human in the Loop” and deployment safeguards:

LLM01: Prompt Injection & LLM02: Insecure Output Handling

  • Human in the Loop: For critical actions (e.g., financial transactions, code deployment), do not rely solely on QWED’s verification. Implement a manual approval step.
  • Output Monitoring: Log and randomly audit verified outputs to ensure the verification engine itself hasn’t been bypassed.

LLM05: Supply Chain Vulnerabilities

  • Startup environment integrity: QWED enforces environment integrity at startup by verifying all Python .pth startup hook files against a built-in allowlist. If any unrecognized .pth file is found, the server refuses to start. You can extend the allowlist for custom deployments using the QWED_ALLOWED_STARTUP_PTH_FILES environment variable (comma-separated list of filenames).
    export QWED_ALLOWED_STARTUP_PTH_FILES="my_custom_plugin.pth,internal_tool.pth"
    
    If you need to bypass the environment integrity check entirely (for example, in development environments with non-standard .pth files), set QWED_SKIP_ENV_INTEGRITY_CHECK=true. The server will log a warning when this bypass is active.
    # Development only — do not use in production
    export QWED_SKIP_ENV_INTEGRITY_CHECK=true
    
  • Startup hook detection: Use StartupHookGuard to scan for malicious .pth files in Python site-packages before your application starts. This defends against supply chain attacks that inject code-execution hooks via compromised PyPI packages.
  • Network Isolation: Run the QWED backend in a VPC without direct outbound internet access, except to specific LLM provider APIs (allowlist).
  • Dependency Scanning: Regularly scan your deployment container for vulnerabilities in system packages.

LLM06: Sensitive Information Disclosure

  • Data Minimization: Do not send PII (Personally Identifiable Information) to QWED unless absolutely necessary. Mask or redact sensitive data before it reaches the API.
For related deployment controls, see Architecture Overview, SDK Guards, and Troubleshooting Guide.

6. Reporting a vulnerability

If you discover a security vulnerability in QWED, do not report it through public GitHub issues, pull requests, or discussions. Instead, report it privately via email to rahul@qwedai.com. If GitHub private vulnerability reporting is enabled for the repository, you may use that channel as well. Include as much detail as possible:
  • Steps to reproduce the issue
  • Affected version(s)
  • Relevant code, configuration, logs, or screenshots
  • Proof-of-concept or exploit details, if available
  • The potential impact on confidentiality, integrity, or availability

Response timeline

  • Your report will be acknowledged within 24 hours
  • The team will triage and validate the report as quickly as possible
  • You will be kept informed of progress during investigation and remediation
  • Disclosure timing will be coordinated with you when appropriate

Coordinated disclosure

Please give the maintainers a reasonable amount of time to investigate and remediate the issue before making any public disclosure. You should:
  • Avoid publicly disclosing the issue until a fix or mitigation is available
  • Make a good-faith effort to avoid privacy violations, data destruction, or service disruption
  • Avoid accessing, modifying, or exfiltrating data beyond what is necessary to demonstrate the issue

Security issue vs. bug

  • Security issue — A vulnerability that compromises the confidentiality, integrity, or availability of the system, such as code execution, injection, auth bypass, privilege escalation, sensitive data exposure, sandbox escape, or fail-open security behavior. Report these privately as described above.
  • Bug — A functional defect or unexpected behavior that does not have security implications, such as a UI issue, incorrect calculation, documentation problem, or non-exploitable crash. Report these via the GitHub Issue Tracker.