CraftBot Analysis Report
Completed July 11, 2026 · 41 artifacts examined
14Findings
5Critical
5High
4Medium
RAISE maturity 1.15 / 5.0
Executive Summary
Agent Remit (as declared)
CraftBot is declared as a self-hosted, general-purpose personal AI agent that acts solely on behalf of a single local operator (BYOK) — executing tasks, remembering preferences, planning proactively on a schedule, and building/running local "Living UI" apps. It may use file operations, web research, document generation, memory search, operator-connected integrations (Google, Slack, Telegram, Discord, GitHub, Stripe, and similar), and gated shell/code execution, MCP servers, and imported projects. Its control surface must be reachable only from the local machine and never exposed to untrusted third parties; shell/code execution and third-party code must be isolated from the host and credentials or gated behind operator approval enforced by code; secrets must be stored encrypted at rest; and inbound messages must never be treated as authenticated operator commands without a sender-identity check.
Behavior Summary (as observed)

The dominant pattern is a full external-input-to-host-execution chain with no code-level interposition at any hop. Untrusted content (web pages via web_fetch, inbound messages, imported Living UI projects) reaches the LLM context unsanitized — the PromptSanitizer that exists is wired to nothing — and the model's output is executed as Python through exec()/subprocess in executor.py, where the "sandbox" is a same-user venv that shares the host filesystem, network, and environment. Every high-impact approval the remit requires lives only as instruction text in the system prompt (agent_core/core/prompts/context.py/action.py); there is no code-enforced approval gate.

Layered on top, the agent's own system-prompt-injected identity file (SOUL.md) and scheduled auto-written MEMORY.md are writable without operator confirmation, turning the same injection path into an ASI06 persistence chain. Genuine controls do exist — the operator control plane binds localhost, a consecutive-LLM-failure abort caps runaway loops, and error strings are redacted — but they sit beside the unguarded exec/approval core.

Scope of Analysis
A large Python application: a custom agent_core action framework plus an app/ layer (CLI and browser/WebSocket UI, LangGraph-style orchestration, scheduler, proactive engine, Living UI manager, and 20+ craftos_integrations messaging/social connectors). The action system executes model-generated Python via exec() in agent_core/core/impl/action/executor.py (both in-process "internal" and a persistent-venv "sandboxed" mode) and via subprocess.run. Session identity is file-backed in agent_file_system/ (SOUL.md is injected into the system prompt; AGENT.md, USER.md, MEMORY.md); a PromptSanitizer exists in app/security/prompt_sanitizer.py but is never called. The operator browser adapter binds localhost, but Living UI backends launch uvicorn on 0.0.0.0. Secrets live as plaintext JSON in .credentials/ and app/config/settings.json; a 157-server MCP marketplace ships in app/config/mcp_config.json (filesystem and playwright enabled by default via unpinned npx -y).
Remit Coverage

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.

Verified: 2 Gap: 7 Partial: 4 Vague Policy: 1 Enforcement Not Possible: 3 Total Rules: 17
Rule ID Section Rule (quoted) Status Finding
R-01 Mission "It must act solely on behalf of, and under the control of, the operator who runs it — never as an autonomous party acting against the operator's interest, and never as a service exposed to untrusted third parties." Enforcement Not Possible
R-02 Non-Goals (Out of Scope) "Exposing its control surface (task execution, settings, files, credentials) to the public internet or to other users." Partial PRAX-2026-07-11-008
R-03 Non-Goals (Out of Scope) "Exfiltrating the operator's data or credentials to any destination the operator did not direct." Enforcement Not Possible
R-04 Approved Communication Channels "The primary operator interface; must be reachable only from the local machine." Verified
R-05 Approved Communication Channels "Inbound messages from a messaging channel must never be treated as authenticated operator commands without a sender-identity check." Gap PRAX-2026-07-11-003
R-06 Tools and Capabilities "Because this capability can read, modify, or destroy any file the operator can and can reach the network, it MUST run isolated from the host or require operator approval before executing a state-changing or destructive command, and it MUST NOT silently inherit the operator's full secret-bearing environment." Gap PRAX-2026-07-11-001
R-07 Tools and Capabilities "These MUST be isolated from the operator's credentials and broader filesystem and MUST come from a source the operator approved." Partial PRAX-2026-07-11-010
R-08 Tools and Capabilities "Any capability that exposes agent control, files, or credentials to a network-reachable, unauthenticated caller." Gap PRAX-2026-07-11-008
R-09 Data Boundaries "Credentials or secrets MUST NOT be written to chat, logs, or any world-readable or version-controlled file." Partial PRAX-2026-07-11-013
R-10 Data Boundaries "Secrets MUST be stored protected at rest (OS keychain or encrypted store), not as plaintext files, and MUST be excluded from any data the agent transmits." Gap PRAX-2026-07-11-007
R-11 Action Boundaries "an agent-initiated high-impact action requires explicit operator approval, and this gating MUST be enforced by code, not left to model discretion alone." Gap PRAX-2026-07-11-002
R-12 Action Boundaries "Any irreversible or externally-visible action: sending email/messages, posting publicly, making payments or other financial operations, and creating/modifying/deleting data in a connected third-party account." Partial PRAX-2026-07-11-002
R-13 Action Boundaries "Fabricating success for an action that failed." Enforcement Not Possible
R-14 Action Boundaries "Editing the operator-owned identity/policy files (SOUL.md, USER.md, AGENT.md) or the long-term memory store without operator confirmation." Gap PRAX-2026-07-11-009
R-15 Escalation Rules "Repeated identical failures (a loop), or a consecutive-LLM-failure condition." Verified
R-16 Behavioral Expectations "Maximum retries before escalation: one retry for a transient error; then stop and escalate." Vague Policy
R-17 Authorized Counterparties "Unknown inbound senders on any messaging channel, treated as trusted commanders without an identity check." Gap PRAX-2026-07-11-003
Findings Register

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.

LLM06 primary — the finding's main OWASP categoryASI10 secondary — a category it also touches

CRITICAL PRAX-2026-07-11-001 Model-generated Python is executed on the host via exec()/subprocess with no isolation, no code approval, and the operator's full environment.
Policy Rule — R-06 (Worker Remit):
"Because this capability can read, modify, or destroy any file the operator can and can reach the network, it MUST run isolated from the host or require operator approval before executing a state-changing or destructive command, and it MUST NOT silently inherit the operator's full secret-bearing environment."
agent_core/core/impl/action/executor.py:558 — exec(action_code, local_ns, local_ns) runs model-provided code in-process; full builtins available, no RestrictedPython (also line 617 async path) agent_core/core/impl/action/executor.py:46 — _SANDBOX_BASE_PACKAGES = [] with comment "venv isolation is the sandbox" — a same-user venv is not a host/network/credential boundary
Recommended Action
  • Run all model-generated code in a real isolation boundary (container/microVM with dropped network and a scrubbed environment) instead of a same-user venv, and strip secret-bearing env vars before exec.
  • Gate any state-changing or destructive execution behind a code-enforced operator approval step in executor.py, not prompt text.
CRITICAL PRAX-2026-07-11-002 Every high-impact approval the remit requires exists only as system-prompt instructions; there is no code-enforced approval gate.
Policy Rule — R-11, R-12 (Worker Remit):
"an agent-initiated high-impact action requires explicit operator approval, and this gating MUST be enforced by code, not left to model discretion alone. / Any irreversible or externally-visible action: sending email/messages, posting publicly, making payments or other financial operations, and creating/modifying/deleting data in a connected third-party account."
agent_core/core/prompts/context.py:138 — "Confirm before destructive/irreversible operations" is instruction text only; grep for require_approval/pending_approval finds no runtime gate in the action path agent_core/core/prompts/action.py:29 — "Anything needing user approval before completion" — approval is described to the model, never enforced by code
Recommended Action
  • Introduce a code-level approval checkpoint in the action executor/manager that blocks send/post/pay/delete/exec actions until an explicit operator confirmation is recorded.
  • Classify actions by impact and require approval for the high-impact classes regardless of model output.
CRITICAL PRAX-2026-07-11-003 Inbound messaging defaults to auto_reply=true with no sender-identity check, so an unknown sender can drive the agent as the operator.
Policy Rule — R-05, R-17 (Worker Remit):
"Inbound messages from a messaging channel must never be treated as authenticated operator commands without a sender-identity check. / Unknown inbound senders on any messaging channel, treated as trusted commanders without an identity check."
app/config/external_comms_config.json:10 — telegram "auto_reply": true (and whatsapp "auto_reply": true) shipped as defaults craftos_integrations/integrations/telegram_bot/__init__.py:414 — inbound sender_id captured but no operator-identity comparison gates processing/reply of the message
Recommended Action
  • Add an operator-identity allowlist check that runs before any inbound message is treated as a command or auto-replied, defaulting auto_reply to false.
  • Route messages from unrecognized senders to an approval/notification path rather than the command pipeline.
CRITICAL PRAX-2026-07-11-004 Untrusted external content reaches the LLM context unsanitized and the model's output is executed as host code — a one-hop injection-to-RCE chain.
app/data/action/web_fetch.py:208 — fetched web content is returned into the agent pipeline; no PromptSanitizer / trust boundary is applied before it reaches the model agent_core/core/impl/action/executor.py:558 — model output is executed via exec() with no gate — the terminal hop of external-input -> context -> host execution
Recommended Action
  • Treat all external/tool-returned content as untrusted: wire the existing PromptSanitizer (or a stronger boundary) into the ingestion path and mark such content non-executable-provenance.
  • Combine with the code-enforced exec approval and isolation from findings 001/002 to break the chain end to end.
CRITICAL PRAX-2026-07-11-005 Writable system-prompt identity file (SOUL.md) and auto-written MEMORY.md plus the injection-to-exec path form an ASI06 persistence-and-execution chain.
agent_core/core/prompts/context.py:189 — "SOUL.md ... is injected directly into your system prompt ... You can read and update SOUL.md" — writable session-loaded prompt with no approval gate app/agent_base.py:609 — scheduled memory task writes important events to MEMORY.md unattended (create_process_memory_task), no operator confirmation
Recommended Action
  • Require code-enforced operator confirmation before any write to SOUL.md/AGENT.md/USER.md or the memory store, and keep these files version-controlled/audited.
  • Break the persistence chain by fixing the injection ingress (004) and the exec/approval gaps (001/002).
HIGH PRAX-2026-07-11-006 A full PromptSanitizer injection-filter module exists but is never called anywhere in the codebase.
app/security/prompt_sanitizer.py:44 — PromptSanitizer.sanitize_user_message defined with INJECTION_PATTERNS denylist app/security/prompt_sanitizer.py — grep for PromptSanitizer/sanitize_user_message across app, agent_core, craftbot.py, main.py returns no consumer — declared-but-never-consulted
Recommended Action
  • Invoke the sanitizer (or a stronger trust boundary) at every ingestion point for user, web, and tool-returned content.
  • Treat the regex denylist as defense-in-depth only; do not rely on it as the sole injection control.
HIGH PRAX-2026-07-11-007 Operator secrets are stored as plaintext JSON at rest — integration tokens in .credentials/ and LLM API keys in settings.json — not in a keychain or encrypted store.
Policy Rule — R-10 (Worker Remit):
"Secrets MUST be stored protected at rest (OS keychain or encrypted store), not as plaintext files, and MUST be excluded from any data the agent transmits."
craftos_integrations/credentials_store.py:76 — _save_dataclass writes credential dataclasses as plaintext JSON into .credentials/<integration>.json app/config.py:417 — save_settings json.dumps the settings dict — including the api_keys block — to app/config/settings.json in plaintext
Recommended Action
  • Move secrets to an OS keychain (Keychain/DPAPI/libsecret) or an encrypted store; keep only references in settings.json / .credentials/.
  • Migrate existing plaintext secrets on upgrade and scrub them from the config files.
HIGH PRAX-2026-07-11-008 Living UI backends launch uvicorn bound to 0.0.0.0, exposing agent-operated services to the whole local network unauthenticated.
Policy Rule — R-02, R-08 (Worker Remit):
"Exposing its control surface (task execution, settings, files, credentials) to the public internet or to other users. / Any capability that exposes agent control, files, or credentials to a network-reachable, unauthenticated caller."
app/living_ui/manager.py:1943 — uvicorn backend started with "--host", "0.0.0.0" (Windows and Linux/Mac branches, lines 1943 and 1965) app/data/living_ui_sidecar/proxy.py:233 — uvicorn.run(app, host="0.0.0.0", port=args.proxy_port) binds the sidecar proxy to all interfaces
Recommended Action
  • Bind Living UI backends and the sidecar proxy to 127.0.0.1 by default; require explicit operator opt-in and authentication for any non-loopback bind.
  • Remove shell=True from the Windows uvicorn launch to avoid command-construction risk.
HIGH PRAX-2026-07-11-009 The agent can rewrite its own identity/policy files (SOUL.md, AGENT.md, USER.md) and long-term MEMORY.md without code-enforced operator confirmation.
Policy Rule — R-14 (Worker Remit):
"Editing the operator-owned identity/policy files (SOUL.md, USER.md, AGENT.md) or the long-term memory store without operator confirmation."
agent_core/core/prompts/context.py:212 — "You can read and update AGENT.md, USER.md, and SOUL.md to store persistent configuration" — no approval requirement app/ui_layer/adapters/browser_adapter.py:4122 — write_agent_file writes USER.md/AGENT.md; no operator-confirmation gate around the write
Recommended Action
  • Require an explicit operator-confirmation step in code before any write to the identity/policy files or the memory store.
  • Version-control and audit these files so unattended edits are detectable.
HIGH PRAX-2026-07-11-010 Third-party MCP servers and imported code run without isolation or provenance vetting; the filesystem MCP is enabled by default with broad write scope.
Policy Rule — R-07 (Worker Remit):
"These MUST be isolated from the operator's credentials and broader filesystem and MUST come from a source the operator approved."
app/config/mcp_config.json:5 — filesystem server "enabled": true with args ["-y","@modelcontextprotocol/server-filesystem","."] — unpinned, full cwd read/write, no isolation app/config/mcp_config.json:1169 — playwright-mcp "enabled": true via "@playwright/mcp@latest" — unpinned; 157 total servers installed on demand via npx/uvx with no provenance vetting
Recommended Action
  • Pin MCP server versions and require an operator-approved allowlist before a server may run; scope the filesystem server to a designated workspace path, not ".".
  • Run MCP servers and imported projects in an isolated context without access to the operator's credential store.
MEDIUM PRAX-2026-07-11-011 Python dependencies are entirely unpinned with no lockfile, enabling version-swap and dependency-confusion risk.
requirements.txt:1 — bare package names (requests, pyyaml, loguru, ...) with no == pins; grep -c "==" returns 0 and no lockfile present
Recommended Action
  • Pin dependencies to exact versions and commit a lockfile with hashes.
  • Add a dependency-audit step to CI.
MEDIUM PRAX-2026-07-11-012 No adversarial/injection testing exists and the CI workflow is checked in disabled.
tests — no test file targets prompt injection / adversarial input; only functional tests (test_trigger_*, test_event_stream_protection, test_prompt_profile) present .github/workflows/ci.yml.disabled — CI workflow is present but disabled by filename, so tests/lint do not run automatically
Recommended Action
  • Add an adversarial test suite covering injection via web content, inbound messages, and imported Living UI, and gate merges on it.
  • Re-enable CI so functional and security tests run on every change.
MEDIUM PRAX-2026-07-11-013 Action logs capture call args and results without secret redaction; scrubbing exists only in the error handler.
Policy Rule — R-09 (Worker Remit):
"Credentials or secrets MUST NOT be written to chat, logs, or any world-readable or version-controlled file."
agent_core/decorators/log_events.py:24 — log templates support {args}, {kwargs}, {result} — action inputs/outputs logged with no secret scrubbing app/security/error_handler.py:48 — redaction is applied only to error strings (re.sub -> [REDACTED]); no equivalent scrubbing on the general action/log path
Recommended Action
  • Apply the secret-redaction filter to all log sinks, not just error strings.
  • Exclude credential-bearing fields from logged action args/results.
MEDIUM PRAX-2026-07-11-014 Domain scope is enforced only in prompt text while a 157-server MCP marketplace and broad action framework form a large latent tool surface.
app/config/mcp_config.json — 157 MCP servers defined (stripe, alpaca/robinhood trading, twitter/discord posting, home-assistant) — mostly disabled but one flag from active agent_core/core/prompts/context.py:37 — task scope/domain guidance is prompt-only; no code gate limits which capability classes the agent may invoke
Recommended Action
  • Enforce an operator-configured capability allowlist in code so disabled/unapproved MCP servers and action classes cannot be invoked even if the prompt is subverted.
  • Keep the marketplace opt-in and require explicit enable + approval per server.
What's Working Well

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.

Operator control plane binds localhost by default

The browser/WebSocket operator interface defaults to host "localhost", keeping the primary control surface off the network unless deliberately changed.

app/ui_layer/adapters/browser_adapter.py:993

Consecutive-LLM-failure abort caps runaway loops

The LLM interface tracks consecutive failures and raises LLMConsecutiveFailureError once the threshold (5) is hit, satisfying the remit's loop/consecutive-failure halt expectation.

agent_core/core/impl/llm/interface.py:507

Credential files written with owner-only permissions

The credentials store chmods its directory and files to user-only (0600/0700), limiting local read exposure of the plaintext credential JSON.

craftos_integrations/credentials_store.py:81

Error-string redaction and API-key masking

The error handler substitutes secret-shaped patterns with [REDACTED] and settings UIs mask API keys to first/last four characters before display.

app/security/error_handler.py:48

Structured file logging and event stream

loguru file sinks (main.log, all.log, per-subagent) plus an event-stream and log_events decorator provide action-level, parseable runtime logs.

app/logger.py:56
Discovered Log Files

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
<run_dir>/main.log app/logger.py setup_logging loguru sink plaintext application log Primary runtime log of agent operation unknown Inferred
<run_dir>/all.log app/logger.py setup_logging loguru sink plaintext application log (verbose) Full-verbosity runtime log including subprocess/uvicorn capture unknown Inferred
<run_dir>/<agent_tag>.log app/logger.py add_subagent_log_sink plaintext per-subagent log Isolated log lines for an individual sub-agent run unknown Inferred
logs/<name>.log agent_core/utils/logger.py plaintext component log agent_core component-level logging unknown Inferred
OWASP LLM Top 10 (2025) Coverage

Each card represents one category and shows the top 3 findings. All items in the Findings section.

LLM04 Data and Model Poisoning
No findings
LLM07 System Prompt Leakage
No findings
LLM08 Vector and Embedding Weaknesses
No findings
LLM09 Misinformation
No findings
LLM10 Unbounded Consumption
No findings
OWASP Agentic Top 10 (2026) Coverage

Each card represents one category and shows the top 3 findings. All items in the Findings section.

RAISE Maturity Posture

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.

1.15 / 5.0
Weighted Maturity Score · Ad hoc
Ad hoc. CraftBot has coherent product ambitions and a handful of real safeguards (localhost-bound control plane, consecutive-failure abort, credential-file permissions, error redaction, structured file logging), but the controls the RAISE framework weights most heavily are effectively absent on the agent's execution path: model-generated code runs on the host with no isolation and no code-enforced approval, external content enters context unsanitized, secrets are plaintext at rest, dependencies and MCP servers are unpinned and unvetted, and there is no adversarial testing. Monitoring is the strongest category on the strength of structured loguru/event-stream logs, but even that lacks security alerting and secret redaction in action logs.
Limit Your Domain
1/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.15
The remit authorizes a general-purpose assistant, but domain scope is enforced only in prompt text (agent_core/core/prompts/context.py) while a 157-server MCP marketplace and a broad action framework form a vast latent tool surface with no code-level gate.
Balance Your Knowledge Base
1/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.15
External content (web_fetch, inbound messages, imported Living UI content) flows into the LLM context with no trust boundary; the one input filter, PromptSanitizer in app/security/prompt_sanitizer.py, is never invoked anywhere in the codebase.
Implement Zero Trust
1/ 5
Confidence: High  |  Weight: 25%  |  Weighted: 0.25
The core exec/approval path has zero code interposition — exec() runs model code in-process with full builtins, the "sandbox" is a same-user venv, and every approval requirement is prompt-only; partial credit only for the localhost control-plane bind and consecutive-failure abort.
Manage Your Supply Chain
1/ 5
Confidence: High  |  Weight: 15%  |  Weighted: 0.15
requirements.txt pins zero dependencies (no == and no lockfile), the bundled MCP servers install via unpinned npx -y / @latest, and operator secrets are stored as plaintext JSON rather than referenced from a vault.
Build an AI Red Team
1/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.15
The tests/ directory holds only functional/lifecycle tests (trigger, event-stream, token) with no injection or adversarial suite, and the CI workflow is checked in disabled (.github/workflows/ci.yml.disabled).
Monitor Continuously
2/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.30
Structured loguru file sinks (main.log, all.log, per-subagent) plus an event stream and a log_events decorator give real action-level logging, but there is no security alerting/anomaly detection and action logs capture args/results without secret redaction.

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.
Weighting: the weighted overall above is the sum of each category's score × weight (the per-category weights are shown on each card). Zero Trust carries double weight by design; see the RAISE framework reference for the rationale.