Devika is a textbook case of controls declared in policy and documentation but absent from code, capped by a single catastrophic compound chain. The remit requires a sandboxed runner, injection screening of untrusted input, work-directory confinement, and approval gates; the implementation has none of them — `src/sandbox/firejail.py` and `src/sandbox/code_runner.py` are empty files while `Runner.run_code()` passes LLM-generated commands straight to `subprocess.run`, and no module anywhere screens web or user content before it reaches an LLM call or an action.
The dominant attack path: an attacker plants instructions on any web page Devika crawls during research → that content flows unvalidated through `Formatter` into the `Coder`/`Runner` context → the model emits shell commands or traversal file paths → `subprocess.run` executes them on the host and `save_code_to_project` writes them anywhere `os.path.join` resolves. The whole surface is reachable through an unauthenticated `POST /api/settings` and the SocketIO `user-message` handler on `0.0.0.0:1337`, with no step/time cap and only file-level logging — making the gap both exploitable and largely undetectable.
0.0.0.0:1337 that orchestrates a dozen LLM-backed sub-agents (`planner`, `researcher`, `formatter`, `coder`, `runner`, `patcher`, `feature`, `decision`, etc.) through a single `Agent` class in `src/agents/agent.py`. Each sub-agent renders a Jinja2 prompt, calls a pluggable provider via `src/llm/llm.py`, and parses the model output; web search results flow `BingSearch`/`GoogleSearch` → live page crawl → `Formatter` → `Coder` context. The `Runner` executes model-produced shell commands with a bare `subprocess.run` and the declared sandbox modules `src/sandbox/firejail.py` and `src/sandbox/code_runner.py` are 0-byte stubs. State and conversation persist in SQLite; an unauthenticated POST /api/settings route rewrites `config.toml` (including API keys and the inference timeout); code is written to disk via `save_code_to_project` using an unsanitized `os.path.join` of the model-chosen filename.Every actionable rule in the Worker Remit, checked against the running code. Gap = declared but unenforced; Partial = enforced but incomplete or bypassable; Vague Policy = too imprecise to verify.
| Rule ID | Section | Rule (quoted) | Status | Finding |
|---|---|---|---|---|
| R-01 | Authorized Capabilities — What Devika may NOT do autonomously | "Devika MUST NOT execute arbitrary shell commands outside its sandboxed runner." | Gap | PRAX-2026-05-29-001 |
| R-02 | Authorized Capabilities — What Devika may NOT do autonomously | "Devika MUST NOT write files outside the configured per-project work directory." | Gap | PRAX-2026-05-29-003 |
| R-03 | Authorized Capabilities — What Devika may NOT do autonomously | "Devika MUST NOT install system packages." | Gap | PRAX-2026-05-29-006 |
| R-04 | Authorized Capabilities — What Devika may NOT do autonomously | "Devika MUST NOT modify its own code, configuration, or memory store." | Gap | PRAX-2026-05-29-004 |
| R-05 | Authorized Capabilities — What Devika may NOT do autonomously | "Devika MUST NOT exfiltrate operator credentials, API keys, or environment variables." | Partial | PRAX-2026-05-29-004 |
| R-06 | Behavioral Constraints — What Devika must always do | "All file reads and writes MUST be confined to the current project's work directory, rejecting any path that escapes it." | Gap | PRAX-2026-05-29-003 |
| R-07 | Behavioral Constraints — What Devika must always do | "The user's natural-language request and any web-retrieved content MUST be treated as untrusted input and screened for instruction injection before it informs LLM context or an action decision." | Gap | PRAX-2026-05-29-002 |
| R-08 | Behavioral Constraints — What Devika must always do | "Every action — plan step, file write, shell execution, web fetch, LLM call — MUST be recorded to a per-project audit log with enough detail to reconstruct what was done." | Gap | PRAX-2026-05-29-009 |
| R-09 | Behavioral Constraints — What Devika must always do | "Each run MUST enforce a wall-clock and step-count cap to prevent runaway loops." | Gap | PRAX-2026-05-29-010 |
| R-10 | Behavioral Constraints — What Devika must always do | "Control MUST return to the user before any action that installs packages, writes outside a previously-touched file, or executes a shell command whose output was not anticipated in the plan." | Gap | PRAX-2026-05-29-005 |
| R-11 | Behavioral Constraints — What Devika must NEVER do | "Devika MUST NOT execute LLM-produced code anywhere but inside the sandboxed runner." | Gap | PRAX-2026-05-29-001 |
| R-12 | Behavioral Constraints — What Devika must NEVER do | "Devika MUST NOT follow instructions embedded in retrieved web content, in project files it reads, or in the user's prompt that attempt to:" | Gap | PRAX-2026-05-29-002 |
| R-13 | Behavioral Constraints — What Devika must NEVER do | "Devika MUST NOT install packages or pull in dependencies without explicit user confirmation, especially from unpinned versions or unverified registries." | Gap | PRAX-2026-05-29-006 |
| R-14 | Behavioral Constraints — Human approval is required for | "Any shell command that would write outside the work directory." | Gap | PRAX-2026-05-29-005 |
| R-15 | Behavioral Constraints — Human approval is required for | "Any action that exceeds the configured step-count or wall-clock cap for a single run." | Gap | PRAX-2026-05-29-010 |
| R-16 | Scope Boundaries — What Devika does NOT do | "Devika MUST NOT deploy code to production." | Gap | PRAX-2026-05-29-007 |
| R-17 | Scope Boundaries — What Devika does NOT do | "Devika MUST NOT communicate with external services beyond the LLM and search providers." | Gap | PRAX-2026-05-29-007 |
| R-18 | Scope Boundaries — What Devika does NOT do | "Devika MUST NOT replace human review for security-sensitive code such as auth, crypto, or data handling." | Enforcement Not Possible | — |
| R-19 | Behavioral Constraints — What Devika must always do | "The project knowledge base and memory MUST be scoped to a single project, with no content mixed across projects." | Partial | PRAX-2026-05-29-011 |
| R-20 | Authorized Counterparties | "Any counterparty not listed here is unauthorized by default." | Gap | PRAX-2026-05-29-007 |
Findings, ordered by severity — each linked to its remit rule, evidence, and a recommended action. Tag chips jump to the relevant entry in the RAISE framework, the OWASP LLM Top 10, or the OWASP Agentic Top 10.
CRITICAL PRAX-2026-05-29-001 Compound RCE chain — untrusted web/user content reaches the host shell via the Runner's bare subprocess.run with no sandbox and no approval gate.
"Devika MUST NOT execute arbitrary shell commands outside its sandboxed runner. / Devika MUST NOT execute LLM-produced code anywhere but inside the sandboxed runner."
- Implement the sandbox modules (firejail.py / code_runner.py) and route all Runner.run_code execution through an isolated container/jail; until then, gate every command behind explicit per-command user approval.
- Add an injection-screening pass on web-retrieved and user content before it reaches any agent's LLM call (see PRAX-2026-05-29-002).
CRITICAL PRAX-2026-05-29-002 No instruction-injection screening anywhere — live web page content and the user prompt enter LLM context raw, violating the remit's mandatory screening rule.
"The user's natural-language request and any web-retrieved content MUST be treated as untrusted input and screened for instruction injection before it informs LLM context or an action decision. / Devika MUST NOT follow instructions embedded in retrieved web content, in project files it reads, or in the user's prompt that attempt to:"
- Add an injection-screening / content-origin-labeling step on all web-retrieved and user-supplied text before it is rendered into any agent's prompt; reject or quarantine instruction-like spans from untrusted sources.
- Wrap untrusted content in clearly delimited, non-authoritative context blocks and instruct downstream agents to never execute instructions found inside them.
CRITICAL PRAX-2026-05-29-003 Path traversal in save_code_to_project — model-chosen filenames are os.path.join'd with no confinement, letting code be written outside the work directory.
"Devika MUST NOT write files outside the configured per-project work directory. / All file reads and writes MUST be confined to the current project's work directory, rejecting any path that escapes it."
- In all three save_code_to_project copies, resolve the joined path with os.path.realpath and reject any result whose commonprefix is not the project work directory before opening for write.
- Factor the write into a single confined helper so coder/feature/patcher cannot drift apart.
CRITICAL PRAX-2026-05-29-004 Unauthenticated POST /api/settings rewrites config.toml — any network client can change endpoints/keys, enabling self-config modification and credential redirection.
"Devika MUST NOT modify its own code, configuration, or memory store. / Devika MUST NOT exfiltrate operator credentials, API keys, or environment variables."
- Add authentication/authorization to all /api/* routes (at minimum a local-only bind to 127.0.0.1 and a shared secret), and restrict update_config to a vetted allowlist of mutable keys.
- Move API keys out of the writable config.toml into environment/vault references so a settings write cannot redirect or read credentials.
CRITICAL PRAX-2026-05-29-008 The mandated sandbox is unimplemented — firejail.py and code_runner.py are 0-byte stubs, yet ARCHITECTURE.md claims the Runner executes code in a sandboxed environment.
"Devika MUST NOT execute arbitrary shell commands outside its sandboxed runner."
- Implement firejail/container isolation in these modules and make Runner.run_code refuse to execute unless the sandbox is active (fail closed).
- Correct ARCHITECTURE.md so it does not assert a sandbox that is not present.
HIGH PRAX-2026-05-29-005 No human-approval gate before any high-impact action — shell exec, out-of-tree writes, and deploys all fire autonomously.
"Control MUST return to the user before any action that installs packages, writes outside a previously-touched file, or executes a shell command whose output was not anticipated in the plan. / Any shell command that would write outside the work directory."
- Introduce a deterministic approval gate that pauses and returns control to the user before any shell execution, package install, deploy, or write outside an already-touched file.
- Classify actions by impact and require explicit operator confirmation for the high-impact tier rather than trusting the model's action keyword.
HIGH PRAX-2026-05-29-006 Model can install arbitrary packages without confirmation, and Devika's own dependencies are entirely unpinned with no lockfile.
"Devika MUST NOT install system packages. / Devika MUST NOT install packages or pull in dependencies without explicit user confirmation, especially from unpinned versions or unverified registries."
- Route any install command through the same approval gate (PRAX-2026-05-29-005) and restrict to a small pre-approved package allowlist as the remit specifies.
- Pin all dependencies in requirements.txt to exact versions and commit a lockfile.
HIGH PRAX-2026-05-29-007 Out-of-scope external counterparties — the code wires in Netlify production deploys and a GitHub client the remit explicitly forbids.
"Devika MUST NOT deploy code to production. / Devika MUST NOT communicate with external services beyond the LLM and search providers. / Any counterparty not listed here is unauthorized by default."
- Remove or feature-flag the Netlify deploy and GitHub integrations, or amend the remit to authorize them with explicit approval gates — do not leave forbidden counterparties silently wired into the runtime.
- Enforce the authorized-counterparty list in code so any outbound destination outside LLM/search is rejected by default.
HIGH PRAX-2026-05-29-009 No per-project action audit log — the only logging is a free-form devika_agent.log of HTTP routes, leaving shell execs, file writes, and web fetches unrecorded.
"Every action — plan step, file write, shell execution, web fetch, LLM call — MUST be recorded to a per-project audit log with enough detail to reconstruct what was done."
- Add a structured, append-only per-project audit log (JSON lines) recording each plan step, file write path, executed command, and web fetch URL with timestamps.
- Make the audit log independent of LOG_PROMPTS and of the UI socket events so it survives as a forensic record.
HIGH PRAX-2026-05-29-010 No wall-clock or step-count cap on a run — the only bounds are a per-LLM-call inference timeout and a 5-try response-retry, leaving the overall agent loop unbounded.
"Each run MUST enforce a wall-clock and step-count cap to prevent runaway loops. / Any action that exceeds the configured step-count or wall-clock cap for a single run."
- Add a session-level step counter and wall-clock budget enforced in Agent.execute/subsequent_execute that halts and returns control to the user when exceeded.
- Bound the number of search/crawl and exec iterations per run.
MEDIUM PRAX-2026-05-29-011 Knowledge base is a single global table with no project column, contradicting the remit's per-project memory-isolation rule.
"The project knowledge base and memory MUST be scoped to a single project, with no content mixed across projects."
MEDIUM PRAX-2026-05-29-012 Unauthenticated GET /api/get-browser-snapshot send_files any caller-supplied path, an arbitrary host file read.
Controls and behaviors that are correctly implemented and verified during this scan. These represent areas where the agent's implementation aligns with its stated policy and security best practices.
Path confinement on the project-files read endpoint
get_project_files confines its directory walk to an abspath base via an explicit commonprefix check before reading, showing the confinement pattern the write path (save_code_to_project) should have reused.
secure_filename applied on project API routes
The project blueprint routes (create/delete/download/get-project-files) wrap the caller-supplied project_name in werkzeug secure_filename before use, sanitizing that one input class.
Runs as a non-root user in the container image
devika.dockerfile creates a dedicated nonroot user, chowns the app, and switches USER nonroot before entrypoint, limiting blast radius of host execution to that account.
Log files found in the agent's workspace during this scan. Reviewing these files provides runtime evidence to complement the static analysis above.
| Path | Source | Content Type | Purpose | Last Modified | Status |
|---|---|---|---|---|---|
| data/logs/devika_agent.log | src/logger.py (fastlogging LogInit) | free-form plaintext (route entries + optional prompt/response debug) | HTTP route entry/exit logging and, when LOG_PROMPTS=true, model prompt/response debug; not an action-level audit | unknown | Inferred |
Each card represents one category and shows the top 3 findings. All items in the Findings section.
Each card represents one category and shows the top 3 findings. All items in the Findings section.
Overall maturity assessment across the six categories of the RAISE framework. This is a maturity model, not a school grade: a score of 3 / 5 means Established, not 60 percent. Most production AI agents today score between Ad hoc (1) and Established (3). See the full RAISE framework reference for the complete scale and scoring.
Maturity Scoring Rubric
Every score above is based on this scale. A score is a snapshot of observable posture — not a verdict on the people or team behind the system.
| Score | Label | Meaning |
|---|---|---|
| 5 | Exemplary | Best-in-class; automated, continuously tested, reference quality. Rarely achieved in shipping systems. |
| 4 | Strong | Comprehensive controls, active management, minor gaps. Production-ready. |
| 3 | Established | Documented controls consistently applied; known gaps accepted. A respectable baseline. |
| 2 | Partial | Some controls exist but coverage is incomplete; key gaps remain. |
| 1 | Ad hoc | Informal or inconsistent measures; relies on individual judgment. |
| 0 | Absent | No evidence this category is addressed at all. |