> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Environment Variables

> Control security-sensitive features with environment variables

Security environment variables control opt-in access to potentially dangerous operations, ensuring secure defaults for RCE and session hijacking prevention.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os
from praisonaiagents import Agent

os.environ["PRAISONAI_ALLOW_LOCAL_TOOLS"] = "true"

agent = Agent(
    name="Tool User",
    instructions="Use tools from tools.py to help the user.",
)
agent.start("Calculate using local tools")
```

The user opts in with `PRAISONAI_ALLOW_LOCAL_TOOLS`; without it, dangerous local tools stay blocked by default.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Security Model"
        App[🚀 App Start] --> Check{🔍 Env Var?}
        Check -->|Set| Allow[✅ Allow Feature]
        Check -->|Unset| Block[🛡️ Block Feature]
        Block --> Safe[💼 Safe Mode]
        Allow --> Risk[⚠️ Risk Mode]
    end
    
    classDef app fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef check fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef safe fill:#10B981,stroke:#7C90A0,color:#fff
    classDef risk fill:#8B0000,stroke:#7C90A0,color:#fff
    
    class App app
    class Check check
    class Allow,Risk risk
    class Block,Safe safe
```

## Quick Start

<Steps>
  <Step title="Simple Usage">
    Enable local `tools.py` loading before starting an agent:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true
    ```

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    agent = Agent(name="Tool User", instructions="Use local tools to help.")
    agent.start("Create a report using tools.py")
    ```
  </Step>

  <Step title="With Configuration">
    Enable job workflows and remote browser access when required:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    export PRAISONAI_ALLOW_JOB_WORKFLOWS=true
    export PRAISONAI_BROWSER_ALLOW_REMOTE=true
    ```

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai --workflow job_workflow.yaml
    praisonai browser --host 0.0.0.0 --port 8080
    ```
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant PraisonAI
    participant SecurityCheck
    participant Feature
    
    User->>PraisonAI: Request Feature
    PraisonAI->>SecurityCheck: Check Env Var
    SecurityCheck-->>PraisonAI: Allowed/Blocked
    alt Environment Variable Set
        PraisonAI->>Feature: Execute
        Feature-->>User: Result
    else Environment Variable Unset
        PraisonAI-->>User: Security Block
    end
```

| Phase       | Action                            | Default Behavior           |
| ----------- | --------------------------------- | -------------------------- |
| **Startup** | Check environment variables       | Block dangerous features   |
| **Request** | Validate security permissions     | Allow only safe operations |
| **Execute** | Run with appropriate restrictions | Fail-safe mode active      |

***

## Environment Variables

### PRAISONAI\_TOOL\_SAFETY

Controls whether dangerous built-in tools (shell exec, file delete/move/copy, code execution) are gated by the approval system.

**Default**: unset → `default` preset active (blocks destructive ops in CI, asks on TTY)

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Disable all tool gating (restore pre-4.6.27 behaviour)
export PRAISONAI_TOOL_SAFETY=off

# Equivalent accepted values: off, full, none, 0, false
PRAISONAI_TOOL_SAFETY=off praisonai code "Clean up logs"
```

| Value                                   | Behaviour                                                    |
| --------------------------------------- | ------------------------------------------------------------ |
| unset                                   | `default` preset — denies destructive ops in CI, asks on TTY |
| `off` / `full` / `none` / `0` / `false` | Bypass all gating (trust the LLM)                            |
| `safe` / `read_only`                    | Block all tools in `DEFAULT_DANGEROUS_TOOLS`                 |
| `default`                               | Explicit default (same as unset)                             |

<Note>
  `--dangerously-skip-approval` on `praisonai code` automatically exports `PRAISONAI_TOOL_SAFETY=off` so that child processes inherit the bypass.
</Note>

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

# Equivalent to PRAISONAI_TOOL_SAFETY=off — tool gating disabled
agent = Agent(
    name="Trusted Coder",
    instructions="Run any tool needed",
    tools=["shell", "write_file", "delete_file"],
    approval="bypass",
)
agent.start("Clean up temp files")
```

***

### PRAISONAI\_ALLOW\_LOCAL\_TOOLS

Controls automatic loading of `tools.py` files from the current working directory.

**Security Risk**: Remote Code Execution (RCE) via malicious tools.py files

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Enable local tools loading
export PRAISONAI_ALLOW_LOCAL_TOOLS=true

# Disable (default - secure)
unset PRAISONAI_ALLOW_LOCAL_TOOLS
```

**Affected Components** (verified against PR #1658 head `83b8b14c`):

* `praisonai` wrapper agent generator (`generate_crew_and_kickoff` and `_run_praisonai`), now delegating to `ToolResolver.get_local_callables()` / `ToolResolver.get_local_tool_classes()`
* `praisonai.tool_resolver.ToolResolver._load_local_tools` (single source of truth; the env-var gate itself is enforced by `praisonai._safe_loader.load_user_module`)
* `praisonai run` YAML workflows (recipe `tools.py` under `_run_yaml_workflow`)
* `praisonai research --tools <file.py>`
* `praisonai chat --rewrite-tools <file.py>` and `--expand-tools <file.py>`
* Generic CLI `_load_tools(tools_path)`
* HTTP API: `praisonai.api.call.import_tools_from_file` (raises `ValueError` if disabled)
* Path-traversal guard: files outside the current working directory are refused even when `PRAISONAI_ALLOW_LOCAL_TOOLS=true`
* `praisonaiagents.workflows.workflows.AgentFlow` (SDK-level: skips `tools.py` next to the workflow class when unset)

<Note>
  Even when `PRAISONAI_ALLOW_LOCAL_TOOLS=true`, the loader refuses any path outside the current working directory. This is a deliberate defence-in-depth layer for HTTP-API callers (`praisonai.api.call.import_tools_from_file`) where the path can come from network input. Move the `tools.py` you want to load into your CWD if you hit `Refusing to exec ... outside working directory.` in the logs.
</Note>

`PRAISONAI_ALLOW_LOCAL_TOOLS` accepts only `true` (case-insensitive) in the **wrapper** (`praisonai`). Values like `1`, `yes`, or `on` are **not** truthy for the wrapper (unlike `PRAISONAI_ALLOW_TEMPLATE_TOOLS`).

<Note>
  **Truthy-value inconsistency across surfaces:** The wrapper (`praisonai`) accepts only `true`; the SDK's `AgentFlow` (`praisonaiagents.workflows.workflows`) accepts `true`, `1`, or `yes`; the SDK's recipe path in `workflows.py` accepts `true`, `1`, `yes`, or `on`. If you rely on `PRAISONAI_ALLOW_LOCAL_TOOLS=1`, the wrapper will reject it even though the SDK's AgentFlow will accept it. This is the SDK's actual behavior — set `true` to be safe across all surfaces.
</Note>

<Tip>
  **CLI equivalent:** `praisonai run --allow-local-tools` sets this env var for the duration of the run and restores the prior value in a `finally`. Prefer the flag for a per-invocation grant — the env var persists for the shell.
</Tip>

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

# This will only work if PRAISONAI_ALLOW_LOCAL_TOOLS=true
agent = Agent(
    name="Tool User",
    instructions="Use tools from tools.py to help the user"
)

agent.start("Calculate using local tools")
```

**Error & Warning Messages**

| When                                                                                                                                                      | Where it appears         | Message                                                                                                                                                       |
| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Env var unset **or** module loads with zero public functions — CLI file-path loaders (`--tools`, `--rewrite-tools`, `--expand-tools`, `research --tools`) | stdout (rich `[yellow]`) | `Warning: No tools loaded from <path> (module has no public functions, or local tools loading is disabled — set PRAISONAI_ALLOW_LOCAL_TOOLS=true to enable).` |
| Env var unset, agent generator                                                                                                                            | logger.warning           | `Refusing to exec %s: set PRAISONAI_ALLOW_LOCAL_TOOLS=true to enable.`                                                                                        |
| Env var unset, HTTP API (`api/call.py`)                                                                                                                   | raised exception         | `ValueError("Local tools loading disabled. Set PRAISONAI_ALLOW_LOCAL_TOOLS=true to enable.")`                                                                 |
| Path outside CWD, env var **set**                                                                                                                         | logger.warning           | `Refusing to exec <path>: outside working directory.`                                                                                                         |
| Path outside CWD via HTTP API, env var **set**                                                                                                            | raised exception         | `LocalToolsDisabled("Refusing to exec <path>: outside working directory.")`                                                                                   |
| Env var unset, SDK `AgentFlow` with `tools.py` present                                                                                                    | logger.debug             | `Skipping tools.py load for <class>: set PRAISONAI_ALLOW_LOCAL_TOOLS=true`                                                                                    |

<Tip>
  **Reading the "No tools loaded from …" warning.** The dual-cause wording is intentional (PR #2935) — an empty result now means either `PRAISONAI_ALLOW_LOCAL_TOOLS` is unset or the target module has no public functions after filtering. If the env var is already set, open the file and check that it exports `def` functions with names that don't start with `_`.
</Tip>

### PRAISONAI\_ALLOW\_TEMPLATE\_TOOLS

Controls implicit `tools.py` autoload by the template tool-override system, both from the current working directory and from a recipe's template directory.

**Security Risk**: Remote Code Execution (RCE) when loading recipes/templates from untrusted sources (e.g. recipes fetched from a remote registry)

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Enable template tools autoload
export PRAISONAI_ALLOW_TEMPLATE_TOOLS=1

# Disable (default - secure)
unset PRAISONAI_ALLOW_TEMPLATE_TOOLS
```

**Default**: unset → disabled\
**Accepted truthy values**: `1`, `true`, `yes`, `on` (case-insensitive, whitespace-stripped)

**Affected Components**:

* `praisonai.templates.tool_override.create_tool_registry_with_overrides`
* `praisonai.templates.tool_override.resolve_tools`

**Note**: Explicit `override_files`, `override_dirs`, and `tools_sources` continue to work without this opt-in and are the recommended way to load custom tools.

<Note>
  `PRAISONAI_ALLOW_TEMPLATE_TOOLS` and `PRAISONAI_ALLOW_LOCAL_TOOLS` are intentionally distinct gates — the wrapper keeps its own template/CWD `tools.py` autoload (with `PRAISONAI_ALLOW_TEMPLATE_TOOLS`) even though the canonical resolver now owns `praisonai-tools` discovery ([PraisonAI#3122](https://github.com/MervinPraison/PraisonAI/issues/3122)). Reason: the wrapper gate is skip-on-error and allows an explicit template directory outside the CWD, which the canonical `PRAISONAI_ALLOW_LOCAL_TOOLS` + CWD-boundary gate does not. Set the one that matches your load path — set both if a recipe ships a `tools.py` in a template directory **and** you also depend on the canonical loader.
</Note>

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai.templates.tool_override import create_tool_registry_with_overrides

# This will only load implicit tools.py if PRAISONAI_ALLOW_TEMPLATE_TOOLS=1
registry = create_tool_registry_with_overrides(include_defaults=True)

# Explicit loading works without the env var
registry = create_tool_registry_with_overrides(
    override_files=["./my_tools.py"],  # Always works
    include_defaults=True
)
```

### PRAISONAI\_AUTH\_CONTENT

Loads the entire credential store from a JSON environment variable, bypassing disk I/O entirely (zero-disk mode). Intended for ephemeral containers/CI where OAuth tokens and API keys must not be persisted.

**Security value**: secret hygiene — not an RCE gate. It reduces on-disk secret sprawl. The env var itself must be treated as a secret; inject it only from a secrets manager, never commit it, and prefer a CI runtime that scrubs env vars from build logs.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Inject the whole store in memory; nothing is written to disk
export PRAISONAI_AUTH_CONTENT='{"openai":{"api_key":"sk-...","auth_method":"apikey"}}'

# Disable (default - disk store used)
unset PRAISONAI_AUTH_CONTENT
```

**Default**: unset → on-disk store at `~/.praisonai/credentials.json` is used (unchanged behaviour).

**Value shape**: a **JSON object** mapping provider → credential object (not `1`/`true`). Each credential mirrors `ProviderCredential` — `api_key` + `auth_method` for API keys, plus `access_token`, `refresh_token`, `expires_at` for OAuth.

**Affected Components** (verified against PR [#3775](https://github.com/MervinPraison/PraisonAI/pull/3775) head `52f33362`):

* `praisonai_code.cli.configuration.credentials.CredentialStore` — `__init__` (env-blob parsing), `_read_credentials` (returns the in-memory blob), `_write_credentials` (no-ops disk I/O; updates the in-memory store only), and the `is_in_memory` property
* `praisonai_code.cli.commands.auth.auth_list` / `auth_status` — label the `Source` column `env (in-memory)` when the store is in memory

**Error paths**: the store fails fast rather than silently re-enabling disk persistence.

| Value                                     | Behaviour                                            |
| ----------------------------------------- | ---------------------------------------------------- |
| Empty / whitespace only                   | Raises `ValueError`                                  |
| Not valid JSON                            | Raises `ValueError` with the underlying decode error |
| JSON scalar or array (not an object)      | Raises `ValueError`                                  |
| A provider value that isn't a JSON object | Raises `ValueError` naming the offending provider(s) |

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import json, os

os.environ["PRAISONAI_AUTH_CONTENT"] = json.dumps({
    "openai": {"api_key": "sk-...", "auth_method": "apikey"},
})

from praisonaiagents import Agent

agent = Agent(name="CI Agent", instructions="Answer briefly")
agent.start("What is 2+2?")
# No ~/.praisonai/credentials.json is created — everything stays in memory.
```

<Note>
  Precedence is env blob > disk file. Per-provider API-key env vars (`OPENAI_API_KEY`, …) still win for that specific provider. See [Auth → Zero-disk mode](/docs/cli/auth#zero-disk-mode-praisonai_auth_content) for the full reference.
</Note>

### PRAISONAI\_CONFIG\_CONTENT

Supplies the entire user-config layer as an inline JSON/YAML blob parsed in memory. When set, PraisonAI never reads `~/.praisonai/config.yaml` or a walk-up `praisonai.yaml` project file — the direct sibling of `PRAISONAI_AUTH_CONTENT` for a fully zero-disk run in ephemeral containers/CI.

**Security value**: secret hygiene / stateless-run control — not an RCE gate. It keeps `config.yaml` (MCP server blocks, permissions arrays, model allowlists, hooks) off disk. Treat the env var itself as a secret: inject it from a secrets manager, never commit it, and only use it in CI runtimes that scrub env vars from build logs.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Inline JSON — nothing on disk is read for the user-config layer
export PRAISONAI_CONFIG_CONTENT='{"agent":{"model":"gpt-4o-mini"},"mcp":{"srv":{"url":"https://mcp.example.com"}}}'
praisonai run "Summarise today's docs changes"
# No ~/.praisonai/config.yaml or ./praisonai.yaml is read.
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Inline YAML (multiline via $'...')
export PRAISONAI_CONFIG_CONTENT=$'agent:\n  model: gpt-4o-mini\npermissions:\n  deny:\n    - "rm -rf /"\n'
praisonai run "Answer briefly"
```

**Default**: unset → global + project file discovery (unchanged behaviour).

**Value shape**: any valid YAML mapping — JSON works because JSON is a strict subset of YAML. Same schema as an on-disk `config.yaml`. The value must be a **mapping**, not a list or scalar.

**Interpolation**: `${VAR}`, `{env:VAR}`, and `{file:...}` directives still resolve — the blob composes with the same conventions as an on-disk file.

**Precedence**: occupies the same slot as discovered global/project files. Inline content wins over `PRAISONAI_CONFIG` (path). Per-key env vars (`PRAISONAI_MODEL`, …) and CLI flags still override it; managed policy is still enforced on top.

**Validation / error paths** (verified against PR [#3983](https://github.com/MervinPraison/PraisonAI/pull/3983) head `382c6fd6`):

| Value                                        | Behaviour                                                                                                                |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Invalid JSON/YAML                            | Emits `UserWarning: "Ignoring PRAISONAI_CONFIG_CONTENT: value is not valid JSON/YAML."` and falls back to file discovery |
| Valid non-mapping (list, scalar)             | Emits `UserWarning: "Ignoring PRAISONAI_CONFIG_CONTENT: value must be a mapping."` and falls back to file discovery      |
| Empty valid mapping (`{}` or empty YAML doc) | Authoritative — treated as an empty user-config layer; disk files are still ignored                                      |

**Usage Example** (auth + config in one env set):

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
export PRAISONAI_AUTH_CONTENT='{"openai":{"api_key":"sk-...","auth_method":"apikey"}}'
export PRAISONAI_CONFIG_CONTENT='agent:
  model: gpt-4o-mini'
praisonai run "Run my CI task"
# Nothing is written to ~/.praisonai — both auth and config live in memory only.
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import json, os
from praisonaiagents import Agent

os.environ["PRAISONAI_CONFIG_CONTENT"] = json.dumps({
    "agent": {"model": "gpt-4o-mini"},
    "permissions": {"deny": ["rm -rf /"]},
})

agent = Agent(name="CI Agent", instructions="Answer briefly")
agent.start("What is 2+2?")
# No config.yaml read; no config.yaml written.
```

<Note>
  Higher-precedence layers still win: per-key env vars (`PRAISONAI_MODEL`, …) and CLI flags (`--model`) override any scalar the blob sets. Managed policy still layers below user config. See [`PRAISONAI_AUTH_CONTENT`](#praisonai_auth_content) for the credential sibling, [Env → Related Environment Variables](/docs/cli/env#related-environment-variables), and [Env Config Injection](/docs/features/env-config-injection) for the full reference and decision diagram.
</Note>

### PRAISONAI\_CONFIG

Names an explicit config file path to load as the user-config layer, replacing the discovered global + project files. Lower precedence than `PRAISONAI_CONFIG_CONTENT` (inline wins). Useful when the config is materialised via a mounted secret (Docker/Kubernetes secret volume or Kubernetes ConfigMap) but you don't want it discoverable via file walk-up.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
export PRAISONAI_CONFIG=/run/secrets/praisonai-config.yaml
praisonai run "Answer briefly"
# Discovered ~/.praisonai/config.yaml + project praisonai.yaml are ignored.
```

**Default**: unset → global + project file discovery (unchanged behaviour).

**Value shape**: an absolute or tilde-expanded path to any file the resolver accepts (YAML/JSON). Discovered global + project files are ignored while this path is used.

**Behaviour on missing / unreadable file**: does **not** hard-fail. Emits `UserWarning: "PRAISONAI_CONFIG points at '<path>', which could not be read as a config file; falling back to file discovery."` and falls back to discovery. A non-mapping file body is skipped the same way.

<Note>
  When both `PRAISONAI_CONFIG_CONTENT` and `PRAISONAI_CONFIG` are set, the inline content wins and this path is ignored (no warning, by design).
</Note>

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    CLI[CLI flags<br/>e.g. --model] -->|highest| PerKey[Per-key env vars<br/>PRAISONAI_MODEL, …]
    PerKey --> UserCfg{User-config layer}
    UserCfg -->|inline wins| Inline[PRAISONAI_CONFIG_CONTENT<br/>in-memory JSON/YAML]
    UserCfg -->|then path| Path[PRAISONAI_CONFIG<br/>explicit file path]
    UserCfg -->|else discovery| Disk[~/.praisonai/config.yaml<br/>+ project praisonai.yaml walk-up]
    Inline --> Managed[Managed policy<br/>PRAISONAI_MANAGED_CONFIG_URL / _DIR]
    Path --> Managed
    Disk --> Managed
    Managed --> Defaults[Built-in defaults]

    classDef top fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef env fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff
    classDef base fill:#189AB4,stroke:#7C90A0,color:#fff

    class CLI top
    class PerKey,Inline,Path env
    class UserCfg decision
    class Disk,Managed result
    class Defaults base
```

The provenance report (`praisonai config provenance`) reports a `layer` of `env-config` for keys supplied this way — `source` is `env:PRAISONAI_CONFIG_CONTENT` for inline content, or the absolute file path for explicit-path mode.

### PRAISONAI\_ALLOW\_PLUGIN\_DISCOVERY

Controls automatic discovery and loading of plugins from `.praisonai/plugins/` (project-level) and `~/.praisonai/plugins/` (user-level) directories.

**Security Risk**: Remote Code Execution (RCE) via malicious third-party plugins discovered on `sys.path`.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Enable plugin auto-discovery
export PRAISONAI_ALLOW_PLUGIN_DISCOVERY=true

# Disable (default - secure)
unset PRAISONAI_ALLOW_PLUGIN_DISCOVERY
```

**Default**: unset → discovery skipped silently (debug log only)\
**Accepted truthy values**: `true`, `1`, `yes` (case-insensitive, whitespace-stripped). `on` is **not** accepted.

**Affected Component**: `praisonaiagents.plugins.manager.PluginManager.discover_and_load_plugins`

**Default behavior when unset**: `discover_and_load_plugins()` is a no-op and returns `0`. A `logger.debug` message fires: `Plugin auto-discovery disabled; set PRAISONAI_ALLOW_PLUGIN_DISCOVERY=true`.

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os
os.environ["PRAISONAI_ALLOW_PLUGIN_DISCOVERY"] = "true"

from praisonaiagents import Agent
from praisonaiagents import PluginManager

manager = PluginManager()
loaded = manager.discover_and_load_plugins()
print(f"Loaded {loaded} plugins")

agent = Agent(
    name="Plugin User",
    instructions="Use discovered plugins to help"
)
agent.start("Run task with plugins")
```

**Workaround when off**: Register plugins explicitly without enabling discovery:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import PluginManager

manager = PluginManager()
manager.register_plugin("my_plugin", my_plugin_module)
```

### PRAISONAI\_NO\_PLUGINS

Suppresses external-plugin discovery for the current process — the inverse of `PRAISONAI_ALLOW_PLUGIN_DISCOVERY`. Use for clean, deterministic baselines while debugging or in CI.

**Security value**: reproduces a known-clean run when triaging whether a hook-injecting plugin is influencing behaviour. Does *not* change persisted `.praisonai/config.yaml` state.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Skip external plugins for every command in this shell
export PRAISONAI_NO_PLUGINS=1

# Restore default behaviour
unset PRAISONAI_NO_PLUGINS
```

**Default**: unset → plugins load normally\
**Accepted truthy values**: `true`, `1`, `yes` (case-insensitive, whitespace-stripped). `on` is **not** accepted.

**Affected Component**: `praisonaiagents.plugins.manager.PluginManager.discover_entry_points` and `.auto_discover_plugins` — both short-circuit to `0` when the var is truthy or the constructor was called with `disabled=True`.

**Precedence**: `PluginManager(disabled=True|False)` constructor param > `PRAISONAI_NO_PLUGINS` env var > default (load plugins).

<Tip>
  **CLI equivalent:** `praisonai run --pure` / `chat --pure` / `code --pure` (long alias `--no-plugins`) sets the env var for the duration of the run and restores the prior value in a `finally`. Prefer the flag for a per-invocation grant — the env var persists for the shell. See [Pure Mode](/docs/features/pure-mode).
</Tip>

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.plugins import PluginManager

# Forced-off regardless of the env var (Python parity for --pure)
manager = PluginManager(disabled=True)
manager.discover_entry_points()   # returns 0; no plugins loaded
```

### PRAISONAI\_PROJECT\_ROOT

Sets the allowed root directory for agent output file writes. Writes outside this root are silently blocked.

**Security Risk**: Path-traversal write outside the project tree (e.g. `output_file="../../etc/passwd"`).

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Set a specific project root
export PRAISONAI_PROJECT_ROOT=/home/user/myproject

# Default: current working directory at save time
unset PRAISONAI_PROJECT_ROOT
```

**Default**: unset → `os.getcwd()` at save time\
**Affected Component**: `praisonaiagents.agent.memory_mixin.MemoryMixin._save_output_to_file`

**Silent-failure semantics**: When the output path resolves outside the project root, the save returns `False`, logs `Output file %r is outside project root %r; skipping save` at `logging.warning`, and prints `⚠️ Output path outside project root: <path>` to stdout. No exception is raised.

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os
os.environ["PRAISONAI_PROJECT_ROOT"] = "/home/user/myproject"

from praisonaiagents import Agent

# This saves successfully — path is within project root
agent = Agent(
    name="Writer",
    instructions="Write a report",
    output_file="report.md"
)
agent.start("Summarise the project")

# This is blocked — path escapes the project root
agent2 = Agent(
    name="Writer",
    instructions="Write a report",
    output_file="../report.md"
)
agent2.start("Summarise the project")
# Prints: ⚠️ Output path outside project root: ../report.md
```

### ALLOW\_LOCAL\_CRAWL

Bypasses SSRF protection for loopback, private, link-local, multicast, and unspecified IP addresses in web crawl tools.

**Security Risk**: Server-Side Request Forgery (SSRF) — agents fetching `http://169.254.169.254/...` (cloud metadata), `http://localhost:6379` (Redis), etc.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Allow crawling local/private addresses
export ALLOW_LOCAL_CRAWL=true

# Disable (default - secure)
unset ALLOW_LOCAL_CRAWL
```

**Default**: unset → loopback/private/link-local/multicast/unspecified IPs blocked; returns `{"error": "URL blocked by SSRF policy"}` per blocked URL\
**Accepted truthy values**: `true` only — **exact, case-sensitive match**. This is stricter than other env vars on this page.

**Affected Components**:

* `praisonaiagents.tools.web_crawl_tools` — `_is_safe_crawl_url` calls `is_safe_http_url(url)` with **no** allowlist, so the crawler goes through the strict path.
* `praisonaiagents.tools.url_safety.is_safe_http_url` — accepts an explicit `allowlist` keyword; only the **web crawler** uses the no-allowlist path. SearXNG passes its own loopback allowlist (see [`SEARXNG_URL_ALLOWLIST`](#searxng-url-allowlist)).

<Note>
  `web_crawl` blocks loopback (`127.0.0.1`, `localhost`, `::1`) **by default** — set `ALLOW_LOCAL_CRAWL=true` to reach a local dev server. SearXNG reaches loopback **by design** and does **not** need this flag. That is why `localhost:32768` works for SearXNG search while `127.0.0.1` stays blocked for crawling.
</Note>

One predicate (`is_safe_http_url`) serves both callers; the SearXNG path opts into a loopback allowlist, the crawler passes none.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    U[Agent requests a URL] --> Q{Which caller?}
    Q -->|web_crawl| C[is_safe_http_url<br/>no allowlist]
    Q -->|SearXNG| S[validate_searxng_url<br/>loopback allowlist]
    C --> IP{IP class?}
    S --> IP
    IP -->|loopback + allowlisted| OK[Allowed]
    IP -->|loopback, not allowlisted| NO[Blocked]
    IP -->|private / link-local / metadata| NO
    IP -->|public| OK

    classDef input fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef proc fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef cfg fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef ok fill:#10B981,stroke:#7C90A0,color:#fff
    classDef warn fill:#F59E0B,stroke:#7C90A0,color:#fff

    class U input
    class Q,IP cfg
    class C,S proc
    class OK ok
    class NO warn
```

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os

# Without ALLOW_LOCAL_CRAWL — blocked
from praisonaiagents import Agent

agent = Agent(
    name="Crawler",
    instructions="Fetch the given URL"
)
result = agent.start("Crawl http://192.168.1.1/api/status")
# Returns: {"error": "URL blocked by SSRF policy"}

# With ALLOW_LOCAL_CRAWL=true — allowed
os.environ["ALLOW_LOCAL_CRAWL"] = "true"
result = agent.start("Crawl http://192.168.1.1/api/status")
# Fetches and returns content
```

<Warning>
  `ALLOW_LOCAL_CRAWL` uses **exact** `"true"` matching (case-sensitive). `"True"`, `"TRUE"`, `"1"`, and `"yes"` are all rejected. This differs from the `PRAISONAI_*` variables on this page.
</Warning>

### SEARXNG\_URL\_ALLOWLIST

Adds extra hostnames the **SearXNG** search call sites may reach, on top of the built-in loopback set (`localhost`, `127.0.0.1`, `::1`). For self-hosting SearXNG on a custom host that resolves to loopback.

**Security Risk**: Server-Side Request Forgery (SSRF) — a misconfigured allowlist that reached private ranges could expose internal services. This env var is designed so it **cannot**: entries can only exempt the loopback class.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Allow a self-hosted SearxNG on a named loopback alias, in addition to localhost
export SEARXNG_URL_ALLOWLIST=searxng.local,my-searxng

# Disable (default) — only the built-in loopback set is allowed
unset SEARXNG_URL_ALLOWLIST
```

**Scope**: SearXNG only. The web crawler (`web_crawl` / `_is_safe_crawl_url`) does **not** read this list.\
**Format**: comma-separated hostnames — whitespace is trimmed and each host is lowercased. Empty or unset → only the built-in loopback set applies.\
**Default**: unset → SearXNG reaches only `localhost`, `127.0.0.1`, `::1`.

**Affected Components**:

* `praisonaiagents.tools.url_safety._env_allowlist` — reads `SEARXNG_URL_ALLOWLIST`
* `praisonaiagents.tools.url_safety.validate_searxng_url` — merges the env list with the built-in loopback set and passes it as the `allowlist` to `is_safe_http_url`
* `praisonaiagents.tools.searxng_tools.searxng_search` and `praisonaiagents.tools.web_search` (SearXNG provider)

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os

# Allow a self-hosted SearxNG reachable via a custom loopback hostname
os.environ["SEARXNG_URL_ALLOWLIST"] = "searxng.local"

from praisonaiagents.tools import searxng_search

# Reaches http://searxng.local:32768/search because the host is allowlisted
results = searxng_search("AI news", searxng_url="http://searxng.local:32768/search")
```

<Warning>
  An allowlist entry can only exempt the **loopback** address class. Any host that resolves to a private, link-local, multicast, or unspecified address stays blocked — even when allowlisted. So `SEARXNG_URL_ALLOWLIST=169.254.169.254` can **not** reach cloud metadata, and `10.x` / `192.168.x` ranges remain blocked. The IP-class checks run before the allowlist is consulted.
</Warning>

### PRAISONAI\_ALLOW\_JOB\_WORKFLOWS

Controls execution of job and hybrid workflow types that can run shell commands and scripts.

**Security Risk**: Remote Code Execution (RCE) via malicious YAML workflows

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Enable job workflows
export PRAISONAI_ALLOW_JOB_WORKFLOWS=true

# Disable (default - secure)  
unset PRAISONAI_ALLOW_JOB_WORKFLOWS
```

**Workflow Types Affected**:

* **Job workflows**: Direct shell, Python, and script execution
* **Hybrid workflows**: Combined agent + job execution

**Usage Example**:

```yaml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# job_workflow.yaml
type: job
steps:
  - name: setup
    shell: |
      echo "Setting up environment"
      pip install requirements.txt
      
  - name: process
    python: |
      import os
      result = os.listdir(".")
      print(f"Files: {result}")
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Only works with PRAISONAI_ALLOW_JOB_WORKFLOWS=true
praisonai --workflow job_workflow.yaml
```

### PRAISONAI\_BROWSER\_ALLOW\_REMOTE

Controls browser server binding to non-loopback interfaces (0.0.0.0, remote IPs).

**Security Risk**: WebSocket session hijacking and unauthorized browser access

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Enable remote browser access
export PRAISONAI_BROWSER_ALLOW_REMOTE=true

# Disable (default - secure, localhost only)
unset PRAISONAI_BROWSER_ALLOW_REMOTE
```

**Default Behavior**:

* Binds to `127.0.0.1` (localhost only)
* Blocks attempts to bind to `0.0.0.0` or remote interfaces

**Usage Example**:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai.browser import BrowserServer

# This will only bind to 0.0.0.0 if PRAISONAI_BROWSER_ALLOW_REMOTE=true
# Otherwise falls back to 127.0.0.1
server = BrowserServer(host="0.0.0.0", port=8080)
server.start()
```

### PRAISONAI\_RUN\_SYNC\_TIMEOUT

Default maximum seconds the wrapper's sync-to-async bridge will wait for a coroutine to complete.

**Default:** `300` (5 minutes)

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Tighten for latency-sensitive servers
export PRAISONAI_RUN_SYNC_TIMEOUT=30

# Loosen for long-running batch jobs
export PRAISONAI_RUN_SYNC_TIMEOUT=3600
```

**Resolved per call, not at import.** The wrapper bridge reads and parses this value on every `run_sync` call via `_default_timeout()`. A malformed value (e.g. `notanumber`) falls back to `300.0` instead of crashing `import praisonai`, and a late-set value (dotenv loaded after import, per-request reconfig) takes effect on the next call.

<Note>
  Passing `timeout=None` explicitly to the bridge opts into an **unbounded** wait — the value is not read from this env var. The sync-scheduler bridges use `timeout=None` so a long-running claimed job is never cancelled after 300 s. See [Async Bridge → Timeout resolution](/docs/features/async-bridge#timeout-resolution-omitted-vs-none-vs-number).
</Note>

Applies to:

* Every `praisonai` CLI entry and wrapper-based server (gateway, a2u, mcp\_server, scheduler) via `praisonai._async_bridge.run_sync`.
* Every ACP/LSP agent-centric tool call in `praison "…"` and `praisonai tui launch` via the sibling `praisonai_code.cli.features.agent_tools._run_sync` bridge (added in PR [#3361](https://github.com/MervinPraison/PraisonAI/pull/3361)).

The SDK (`praisonaiagents`) uses its own separate bridge — see [Async Bridge](/docs/features/async-bridge) for the full map.

### PRAISONAI\_ALLOW\_LOOP\_BLOCKING

Opts back into the pre-[#4261](https://github.com/MervinPraison/PraisonAI/pull/4261) behaviour where `praisonai._async_bridge.run_sync_or_offload()` offloads a coroutine onto a worker thread and joins it from inside a running event loop.

**Default**: unset → strict mode. From inside a running loop, `run_sync_or_offload()` **raises `RuntimeError`** instead of blocking.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Opt back into the legacy offload-and-join behaviour (interim migration only)
export PRAISONAI_ALLOW_LOOP_BLOCKING=true

# Disable (default - strict, never blocks the loop)
unset PRAISONAI_ALLOW_LOOP_BLOCKING
```

**Accepted truthy values**: `1`, `true` (case-insensitive).

<Warning>
  When set, the offload path joins the worker thread for up to `PRAISONAI_RUN_SYNC_TIMEOUT + 1s` (default 301s), which **blocks the caller's event loop for the full duration** — FastAPI handlers, Jupyter cells, and async tests will stall. Only use it for a bounded migration window. New code should `await praisonai.arun(...)` / `arun_sync_or_offload(...)` instead.
</Warning>

**Affected Component**: `praisonai._async_bridge.run_sync_or_offload` (and every wrapper sync entry point that reaches it inside a loop, e.g. `PraisonAIAdapter.run()` → `praisonai.run(...)`).

In strict mode (default), a call from inside a running loop prints this `RuntimeError` — grep the traceback for it:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
run_sync_or_offload() would block the running event loop for up to 300s. From an async context, await one of:
  - praisonai.arun(...)
  - adapter.arun(...)
  - praisonai._async_bridge.arun_sync_or_offload(coro)
Set PRAISONAI_ALLOW_LOOP_BLOCKING=true to opt into the legacy blocking behaviour.
```

See [Async Bridge → `run_sync_or_offload`](/docs/features/async-bridge#run-sync-or-offload-strict-inside-a-running-loop) for the full decision diagram.

### PRAISONAI\_SHIM\_LAZY

Opts **out** of the eager subtree walk in `praisonai.cli._shim._register_submodules`, falling back to pure lazy `_AliasFinder` resolution for the `praisonai.bots` / `praisonai.gateway` / `praisonai.daemon` alias shims.

**Default**: unset → eager registration (required for module-identity correctness).

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# Skip the eager subtree walk (faster shim install)
export PRAISONAI_SHIM_LAZY=1

# Disable (default - eager, module-identity preserved)
unset PRAISONAI_SHIM_LAZY
```

**Accepted truthy values**: `1`, `true` (case-insensitive).

<Warning>
  Eager registration is **required for module identity** across the old and new dotted paths. With `PRAISONAI_SHIM_LAZY=1`, `old_name.sub is new_name.sub` may become `False`, breaking `unittest.mock.patch("old_name.sub.attr")` on code that imports the moved package under the **new** name.
</Warning>

**When to use** (both must hold):

* Nothing in the process relies on `mock.patch` against the old dotted path (`praisonai.bots.*`, `praisonai.gateway.*`, `praisonai.daemon.*`).
* Minimal-container startup speed matters more than mock-patch parity — the eager walk pulls the moved subtree (and any optional heavy deps) at shim-install time.

<Note>
  As of [#4261](https://github.com/MervinPraison/PraisonAI/pull/4261), a failed eager submodule import is no longer silently swallowed — it logs a warning with traceback (`shim: eager import of %s failed; will be resolved lazily`) and is left for `_AliasFinder` to resolve lazily. `None` placeholders in `sys.modules` are skipped instead of poisoning the alias.
</Note>

See [Wrapper → alias\_package shim: lazy vs eager](/docs/developers/wrapper#alias-package-shim-lazy-vs-eager) for the developer reference.

***

## Common Patterns

<Tabs>
  <Tab title="Development Mode">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Enable all features for development
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true
    export PRAISONAI_ALLOW_TEMPLATE_TOOLS=true
    export PRAISONAI_ALLOW_JOB_WORKFLOWS=true  
    export PRAISONAI_BROWSER_ALLOW_REMOTE=true
    export PRAISONAI_RUN_SYNC_TIMEOUT=30
    export PRAISONAI_TOOL_SAFETY=off   # disable tool gating in dev

    # Add to ~/.bashrc or ~/.zshrc for persistence
    echo 'export PRAISONAI_ALLOW_LOCAL_TOOLS=true' >> ~/.bashrc
    ```
  </Tab>

  <Tab title="Production Mode">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Secure defaults - explicitly unset dangerous variables
    unset PRAISONAI_ALLOW_LOCAL_TOOLS
    unset PRAISONAI_ALLOW_TEMPLATE_TOOLS
    unset PRAISONAI_ALLOW_JOB_WORKFLOWS
    unset PRAISONAI_BROWSER_ALLOW_REMOTE
    unset PRAISONAI_ALLOW_LOOP_BLOCKING   # keep strict: never block the loop
    unset PRAISONAI_SHIM_LAZY             # keep eager: preserve module identity
    export PRAISONAI_RUN_SYNC_TIMEOUT=300

    # Or use systemd service with secure environment
    # /etc/systemd/system/praisonai.service
    [Service]
    Environment="PRAISONAI_ALLOW_LOCAL_TOOLS=false"
    Environment="PRAISONAI_ALLOW_TEMPLATE_TOOLS=false"
    Environment="PRAISONAI_ALLOW_JOB_WORKFLOWS=false"
    Environment="PRAISONAI_BROWSER_ALLOW_REMOTE=false"
    Environment="PRAISONAI_RUN_SYNC_TIMEOUT=300"
    # Leave PRAISONAI_ALLOW_LOOP_BLOCKING and PRAISONAI_SHIM_LAZY unset (secure defaults)
    ```
  </Tab>

  <Tab title="Docker Deployment">
    ```dockerfile theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Secure Docker deployment
    FROM python:3.11-slim

    # Secure defaults - do not set dangerous env vars
    # ENV PRAISONAI_ALLOW_LOCAL_TOOLS=true  # DON'T DO THIS

    COPY . /app
    WORKDIR /app
    RUN pip install praisonai

    # Only enable specific features if needed
    # ENV PRAISONAI_ALLOW_JOB_WORKFLOWS=true  # Only if required

    CMD ["python", "-m", "praisonai"]
    ```
  </Tab>
</Tabs>

***

## Migration Guide

### Upgrading from Vulnerable Versions

<Steps>
  <Step title="Identify Usage">
    Check if you use any of these features:

    * Local `tools.py` files
    * Recipes / templates that ship a `tools.py` and rely on it being implicitly loaded
    * Job or hybrid workflows with shell/script execution
    * Browser server binding to `0.0.0.0`
    * HTTP API callers that pass a `file_path` to `praisonai.api.call.import_tools_from_file` — these now raise `ValueError` until you opt in
  </Step>

  <Step title="Add Environment Variables">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Only add variables for features you actually use
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true      # If you use tools.py
    export PRAISONAI_ALLOW_TEMPLATE_TOOLS=1      # If you rely on implicit template/CWD tools.py autoload
    export PRAISONAI_ALLOW_JOB_WORKFLOWS=true    # If you use job workflows
    export PRAISONAI_BROWSER_ALLOW_REMOTE=true   # If you bind browser to 0.0.0.0
    export PRAISONAI_RUN_SYNC_TIMEOUT=300        # Adjust timeout as needed
    ```
  </Step>

  <Step title="Test Functionality">
    Verify your existing workflows still work:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Test local tools
    praisonai "Use local tools to help me"

    # Test job workflows  
    praisonai --workflow your_job_workflow.yaml

    # Test remote browser
    praisonai browser --host 0.0.0.0
    ```
  </Step>

  <Step title="Review Security">
    Evaluate if you really need each dangerous feature:

    * Can you avoid local tools.py files?
    * Can you use agent workflows instead of job workflows?
    * Can you use localhost-only browser access?
  </Step>
</Steps>

***

## Best Practices

<AccordionGroup>
  <Accordion title="🔒 Principle of Least Privilege">
    Only enable environment variables for features you actively use. Each variable increases your attack surface.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # BAD - Enables everything
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true
    export PRAISONAI_ALLOW_JOB_WORKFLOWS=true
    export PRAISONAI_BROWSER_ALLOW_REMOTE=true

    # GOOD - Only enable what you need
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true  # Only if you use tools.py
    ```
  </Accordion>

  <Accordion title="🏢 Production Environment Isolation">
    Never enable dangerous variables in production unless absolutely necessary. Use staging environments for testing.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Production - secure defaults
    unset PRAISONAI_ALLOW_LOCAL_TOOLS
    unset PRAISONAI_ALLOW_JOB_WORKFLOWS
    unset PRAISONAI_BROWSER_ALLOW_REMOTE

    # Development/Staging - enable as needed
    export PRAISONAI_ALLOW_LOCAL_TOOLS=true
    ```
  </Accordion>

  <Accordion title="📁 File System Security">
    When `PRAISONAI_ALLOW_LOCAL_TOOLS=true` or `PRAISONAI_ALLOW_TEMPLATE_TOOLS=1` is set, ensure your working directory doesn't contain untrusted `tools.py` files. This is especially risky for recipes fetched from remote registries.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Check for tools.py before running
    ls -la tools.py 2>/dev/null && echo "WARNING: tools.py found"

    # Run from clean directory
    mkdir -p /tmp/clean_workspace
    cd /tmp/clean_workspace
    praisonai "Your task here"
    ```
  </Accordion>

  <Accordion title="🌐 Network Security">
    When `PRAISONAI_BROWSER_ALLOW_REMOTE=true`, use firewalls and authentication to protect browser endpoints.

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Use specific IP instead of 0.0.0.0 when possible
    export PRAISONAI_BROWSER_ALLOW_REMOTE=true
    praisonai browser --host 192.168.1.100 --port 8080

    # Consider using reverse proxy with authentication
    # nginx, caddy, or similar with basic auth
    ```
  </Accordion>
</AccordionGroup>

***

## Security Advisories

These environment variables address the following security vulnerabilities:

| Advisory                                                        | Severity | Description                                                    | Environment Variable             |
| --------------------------------------------------------------- | -------- | -------------------------------------------------------------- | -------------------------------- |
| **GHSA-g985-wjh9-qxxc**                                         | High     | RCE via Automatic tools.py Import                              | `PRAISONAI_ALLOW_LOCAL_TOOLS`    |
| **GHSA-xcmw-grxf-wjhj**                                         | High     | Implicit RCE via template/CWD tools.py autoload                | `PRAISONAI_ALLOW_TEMPLATE_TOOLS` |
| **GHSA-vc46-vw85-3wvm**                                         | Critical | RCE via job workflow YAML                                      | `PRAISONAI_ALLOW_JOB_WORKFLOWS`  |
| **GHSA-8x8f-54wf-vv92**                                         | Critical | WebSocket session hijacking                                    | `PRAISONAI_BROWSER_ALLOW_REMOTE` |
| pending                                                         | High     | SSRF via web crawl to loopback/private addresses               | `ALLOW_LOCAL_CRAWL`              |
| [#4189](https://github.com/MervinPraison/PraisonAI/issues/4189) | High     | SSRF — loopback allowlist re-opened `web_crawl` to `127.0.0.1` | `SEARXNG_URL_ALLOWLIST`          |

**CVE IDs**: Pending assignment by GitHub Security Advisory system

**Fixed Versions**:

* **praisonai**: `>=0.0.57`
* **praisonaiagents**: `>=0.0.23`

PR #1583 (2026-04-30) extended `PRAISONAI_ALLOW_LOCAL_TOOLS` enforcement to research/rewrite/expand/recipe tool-loading paths and the HTTP API, and added a CWD-only path constraint as defence-in-depth. No new advisory was filed; the threat model is unchanged from GHSA-g985-wjh9-qxxc.

***

## Related

<CardGroup cols={2}>
  <Card title="Guardrails" icon="shield" href="/docs/features/guardrails">
    Content filtering and safety controls
  </Card>

  <Card title="Permissions" icon="key" href="/docs/features/permissions">
    Agent permission management system
  </Card>
</CardGroup>
