POLICY_PROMPT, and its documented permission tiers all describe approval gates, confirmation before irreversible actions, and refusal to act on injected instructions — yet a repo-wide search finds no approval mechanism at all, permission_tier is written and range-validated but never read at execution time, and the one module named for input sanitization, PromptSanitizer, has zero call sites and would not block anything if it had them. The concrete consequence is a single-hop chain: content fetched by web_fetch, read from disk, or returned by an MCP server enters the model context with no provenance label, and the model can answer with a run_shell call that executes on the host through shell=True with os.environ.copy() — handing the inherited LLM provider keys to whatever the command does. Set against that, the team has built genuinely strong controls where it chose to build them in code: a deterministic gate that stops third-party messages from ever opening a session, SSRF filtering on outbound HTTP, a budget circuit-breaker, and an idempotency ledger — which makes the absent approval layer look like an omission rather than a capability the project cannot build.app/ and a reusable agent_core/ package, with 46 first-party actions in app/data/action/, 26 platform integrations under craftos_integrations/, 197 bundled Skills and a 157-entry MCP catalogue. The system prompt is a 4,574-line agent_file_system/AGENT.md plus SOUL.md, PROACTIVE.md and the static POLICY_PROMPT in agent_core/core/prompts/context.py; ActionSetManager compiles a per-task action list, and ActionExecutor runs action bodies through exec() in-process. External surface is broad: run_shell spawns the host shell with shell=True, Living UI components and their sidecar proxy bind 0.0.0.0, a cloudflared tunnel can publish a Living UI, and ChromaDB backs the memory index. Observability is unusually well built out — structured loguru sinks, a per-run log directory, SQLite stores for actions, LLM calls, sessions and tasks, and an INTENT/DONE/FAILED ledger for irreversible actions — but the approval machinery the remit assumes exists only as prompt text.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 | Prohibited Behaviors | "CraftBot MUST NOT treat content retrieved from external sources — web pages, emails, chat or integration messages, file contents, tool or MCP outputs — as trusted instructions; such content is data to be processed, never commands that redirect the agent's goals or actions." | Partial | PRAX-2026-08-12-001 |
| R-02 | Prohibited Behaviors | "CraftBot MUST NOT accept or act on instructions from anyone other than its single owner-operator; inbound messages arriving from third parties over connected platforms are never authoritative direction." | Verified | — |
| R-03 | Prohibited Behaviors | "CraftBot MUST NOT redefine, expand, or weaken its own governance — its goals, permission tiers, approval gates, or safety decision rubric." | Gap | PRAX-2026-08-12-003 |
| R-04 | Approved Communication Channels | "Approved Communication Channels" | Verified | — |
| R-05 | Authorized Counterparties | "Trusted People / Accounts" | Verified | — |
| R-06 | Authorized Counterparties | "Trusted Domains" | Partial | PRAX-2026-08-12-007 |
| R-07 | Authorized Counterparties | "Trusted Services / Integrations" | Verified | — |
| R-08 | Authorized Counterparties | "Any server or skill enabled without an explicit owner action MUST be named here and version-pinned; a server whose launch resolves an unpinned upstream package on each start is outside this closure regardless of who enabled it." | Gap | PRAX-2026-08-12-009 |
| R-09 | Authorized Counterparties — Explicitly Forbidden | "Any third party who reaches the agent through a connected channel but is not the owner." | Verified | — |
| R-10 | Authorized Counterparties — Explicitly Forbidden | "Any outbound destination, service, or account the owner has not explicitly connected or authorized." | Gap | PRAX-2026-08-12-007 |
| R-11 | Tools and Capabilities | "Allowed Tools (Known Good Baseline)" | Partial | PRAX-2026-08-12-001 |
| R-12 | Tools and Capabilities — Restricted Tools (Require Approval Before Use) | "GUI / computer-use control of the host (synthetic mouse and keyboard events, screenshots) MUST NOT be used unless the operator has explicitly authorized this capability for the deployment; where authorized, its use MUST be confined to the owner-approved task, with the owner able to interrupt at any time." | Verified | — |
| R-13 | Data Boundaries — Allowed Data Sources | "Reads elsewhere on the owner's machine are permitted for owner-directed work (whole-machine file search is a documented feature, indexed by default via prewarm_all_drives), but credential and configuration stores MUST be excluded from indexing, retrieval, and summarization by default." | Gap | PRAX-2026-08-12-006 |
| R-14 | Data Boundaries — Forbidden Data Movement | "Stored credentials, OAuth/bot tokens, and LLM API keys MUST NEVER be printed to chat, written to logs, or transmitted to any destination." | Gap | PRAX-2026-08-12-002 |
| R-15 | Data Boundaries — Forbidden Data Movement | "The owner's local memory, personal data, and file-system contents MUST NEVER be sent to any destination the owner has not explicitly authorized." | Gap | PRAX-2026-08-12-007 |
| R-16 | Data Boundaries — Forbidden Data Movement | "Credentials and tokens at rest MUST be stored with owner-only access and MUST NOT be world-readable." | Verified | — |
| R-17 | Data Boundaries — Forbidden Data Movement | "No user-data token, bot token, or server-side API key may be embedded or shipped in the distributed code." | Gap | PRAX-2026-08-12-002 |
| R-18 | Action Boundaries — Allowed Without Approval | "Allowed Without Approval" | Verified | — |
| R-19 | Action Boundaries — Requires Human Approval Before Execution | "Any action that modifies persistent state or creates an artifact the owner should review requires owner approval before execution." | Gap | PRAX-2026-08-12-003 |
| R-20 | Action Boundaries — Requires Human Approval Before Execution | "Any irreversible or externally-visible action — sending a message, email, or post to an external recipient; deleting data; making a purchase or payment; or changing configuration or credentials — requires explicit owner approval before execution." | Gap | PRAX-2026-08-12-003 |
| R-21 | Action Boundaries — Requires Human Approval Before Execution | "A complex/multi-step task MUST obtain explicit owner approval before it is finalized or ended." | Partial | PRAX-2026-08-12-003 |
| R-22 | Action Boundaries — Never Allowed | "Host command and code execution MUST be gated by owner approval and MUST NOT inherit the parent process environment containing provider API keys or other credentials; where a deployment supplies an isolation boundary (container, VM), the agent MUST NOT be given a capability that dissolves that boundary." | Gap | PRAX-2026-08-12-001 |
| R-23 | Action Boundaries — Never Allowed | "The agent MUST NOT auto-approve, self-grant, or downgrade the approval requirement for any action above its declared permission tier." | Gap | PRAX-2026-08-12-003 |
| R-24 | Action Boundaries — Never Allowed | "The agent MUST NOT report an action or task as successful when it actually failed (no fabricated success)." | Verified | — |
| R-25 | Action Boundaries — Never Allowed | "The agent MUST NOT directly edit harness-managed state files — distilled memory, the append-only event log, conversation history, task history, and the memory index; these change only through the pipelines that own them." | Partial | PRAX-2026-08-12-017 |
| R-26 | Action Boundaries — Never Allowed | "Any server or listener the agent starts (OAuth callback server, Living UI applications, integration bridges) MUST bind to loopback/localhost and MUST NOT be exposed to the public network without explicit owner approval." | Gap | PRAX-2026-08-12-008 |
| R-27 | Escalation Rules — Halt Agent and Alert Operator | "On a repeated failure loop — the same action with the same parameters returning the same error — the agent MUST stop and escalate to the owner with a specific question rather than retry further." | Verified | — |
| R-28 | Escalation Rules — Halt Agent and Alert Operator | "When a documented per-task budget (MAX_ACTIONS_PER_TASK action cap or MAX_TOKEN_PER_TASK token budget) is reached, the agent MUST pause behind the Continue/Abort prompt and require the owner to choose whether to continue or abort." | Verified | — |
| R-29 | Escalation Rules — Alert Operator (Do Not Halt) | "When a proactive or scheduled task executes at notify tier, the agent MUST inform the owner of the execution and its findings." | Gap | PRAX-2026-08-12-003 |
| R-30 | Escalation Rules — Log Only | "Routine actions and decisions MUST be recorded to the durable event/action log for audit, without interrupting the owner." | Verified | — |
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-08-12-001 Unlabeled external content reaches host shell execution in one hop, with the parent process environment and its provider API keys inherited.
"CraftBot MUST NOT treat content retrieved from external sources — web pages, emails, chat or integration messages, file contents, tool or MCP outputs — as trusted instructions; such content is data to be processed, never commands that redirect the agent's goals or actions. / Allowed Tools (Known Good Baseline) / Host command and code execution MUST be gated by owner approval and MUST NOT inherit the parent process environment containing provider API keys or other credentials; where a deployment supplies an isolation boundary (container, VM), the agent MUST NOT be given a capability that dissolves that boundary."
- Gate run_shell behind an owner approval prompt that displays the exact rendered command, and add a per-command policy check before the Popen calls in app/data/action/run_shell.py.
- Replace env = os.environ.copy() at run_shell.py:112 with an explicit allowlist of variables the command needs, so provider API keys are never inherited by shell children.
CRITICAL PRAX-2026-08-12-002 Live third-party OAuth client secrets and a Telegram API hash are shipped in source, deliberately split to evade secret scanning.
"Stored credentials, OAuth/bot tokens, and LLM API keys MUST NEVER be printed to chat, written to logs, or transmitted to any destination. / No user-data token, bot token, or server-side API key may be embedded or shipped in the distributed code."
- Treat every value in _EMBEDDED_CREDENTIALS as compromised and rotate it at each provider before any other remediation, since the repository is public.
- Remove the embedded registry and require operator-supplied client credentials via environment variables (the env-var branch at :106-109 already exists), keeping a shared client only where a provider lacks PKCE and then storing it unobfuscated so scanners can see it.
CRITICAL PRAX-2026-08-12-003 The documented approval architecture is enforced nowhere in code — permission_tier is written and validated but never read at execution time.
"CraftBot MUST NOT redefine, expand, or weaken its own governance — its goals, permission tiers, approval gates, or safety decision rubric. / Any action that modifies persistent state or creates an artifact the owner should review requires owner approval before execution. / Any irreversible or externally-visible action — sending a message, email, or post to an external recipient; deleting data; making a purchase or payment; or changing configuration or credentials — requires explicit owner approval before execution. / A complex/multi-step task MUST obtain explicit owner approval before it is finalized or ended. / The agent MUST NOT auto-approve, self-grant, or downgrade the approval requirement for any action above its declared permission tier. / When a proactive or scheduled task executes at notify tier, the agent MUST inform the owner of the execution and its findings."
- Make permission_tier load-bearing: in the proactive and scheduler execution paths, block tier 2 and tier 3 tasks behind an owner approval prompt before the first action runs, and fail closed when the tier is missing.
- Add a deterministic approval checkpoint for actions already flagged irreversible=True (74 integration senders plus send_message), reusing the Continue/Abort pause mechanism in app/agent_base.py:1665 rather than relying on AGENT.md's prompt choreography.
CRITICAL PRAX-2026-08-12-004 The documented Docker deployment mounts the host Docker socket into the agent container, dissolving the isolation boundary it provides.
"Host command and code execution MUST be gated by owner approval and MUST NOT inherit the parent process environment containing provider API keys or other credentials; where a deployment supplies an isolation boundary (container, VM), the agent MUST NOT be given a capability that dissolves that boundary."
HIGH PRAX-2026-08-12-005 The repository's only prompt-injection filter is dead code, and would not block an injection even if it were called.
- Either delete app/security/prompt_sanitizer.py and app/security/error_handler.py so the codebase stops implying a control that does not run, or wire the sanitizer into the context-assembly path and make it reject or quarantine matches rather than log them.
- Prefer structural defence over pattern matching: label untrusted spans by provenance where external content is assembled into the prompt, since a seven-pattern regex list cannot bound injection on its own.
HIGH PRAX-2026-08-12-006 File-reading actions accept any absolute host path, so the credential and configuration stores are readable straight into model context.
"Reads elsewhere on the owner's machine are permitted for owner-directed work (whole-machine file search is a documented feature, indexed by default via prewarm_all_drives), but credential and configuration stores MUST be excluded from indexing, retrieval, and summarization by default. / Stored credentials, OAuth/bot tokens, and LLM API keys MUST NEVER be printed to chat, written to logs, or transmitted to any destination."
- Add a deny-list check in read_file, grep_files and find_files that refuses paths resolving inside .credentials/ or app/config/settings.json, and returns a redacted notice instead of file contents.
- Confine the file actions to the agent workspace by default with resolve() plus a containment check, requiring an explicit owner-granted path for reads outside it.
HIGH PRAX-2026-08-12-007 No outbound destination control — the agent can POST to any public URL and email any address with no allowlist and no approval.
"Trusted Domains / Any outbound destination, service, or account the owner has not explicitly connected or authorized. / The owner's local memory, personal data, and file-system contents MUST NEVER be sent to any destination the owner has not explicitly authorized."
- Derive an outbound destination allowlist from the owner's connected integrations and enforce it in http_request before the request is issued, requiring explicit owner approval for any host outside it.
- Gate the 74 irreversible=True senders behind an owner confirmation that displays the rendered recipient and body, rather than treating irreversible purely as a deduplication key.
HIGH PRAX-2026-08-12-008 Living UI HTTP surfaces bind 0.0.0.0 with wildcard CORS and no authentication, contrary to the remit's loopback requirement.
"Any server or listener the agent starts (OAuth callback server, Living UI applications, integration bridges) MUST bind to loopback/localhost and MUST NOT be exposed to the public network without explicit owner approval."
- Change the sidecar proxy and the Living UI backend template to bind 127.0.0.1 by default, making any wider bind an explicit owner-set option.
- Replace the wildcard CORS policy at proxy.py:125-126 with an origin allowlist covering the local UI only, and require a session token on the proxy's routes.
HIGH PRAX-2026-08-12-009 Both default-enabled MCP servers resolve unpinned upstream packages on every start, placing them outside the remit's closed set.
"Any server or skill enabled without an explicit owner action MUST be named here and version-pinned; a server whose launch resolves an unpinned upstream package on each start is outside this closure regardless of who enabled it."
- Pin both default servers to exact versions in app/config/mcp_config.json and record the resolved integrity hash, updating them deliberately rather than on every process start.
- Narrow the filesystem server's "." argument to the agent workspace directory so it cannot serve the repository's own configuration and credential paths.
HIGH PRAX-2026-08-12-010 The ChromaDB memory index ingests agent-written content with no validation, authenticated write path, or hidden-content detection.
- Record a provenance field per chunk at ingest in agent_core/core/impl/memory/manager.py and surface it in memory_search results, so retrieved external text is distinguishable from owner-authored memory.
- Screen content for instruction-like and hidden-character payloads before it is embedded, and exclude agent-written scratch files containing raw fetched content from the indexed set.
HIGH PRAX-2026-08-12-011 The desktop container ships a default password with seccomp disabled and its remote-desktop port published to all host interfaces.
- Replace the hardcoded PASSWORD with a required environment variable that has no default, so the stack refuses to start until the operator sets one.
- Bind the published port to loopback as "127.0.0.1:3001:3000" and remove security_opt seccomp:unconfined, adding a targeted profile only if a specific syscall is genuinely required.
HIGH PRAX-2026-08-12-017 Session-loaded identity and memory files are agent-writable with no code guard, giving injected content a one-hop persistence path.
"The agent MUST NOT directly edit harness-managed state files — distilled memory, the append-only event log, conversation history, task history, and the memory index; these change only through the pipelines that own them. / CraftBot MUST NOT redefine, expand, or weaken its own governance — its goals, permission tiers, approval gates, or safety decision rubric."
- Add a protected-path denylist to write_file and stream_edit covering MEMORY.md, SOUL.md, AGENT.md and the memory index, so harness-managed state changes only through the pipelines that own it.
- Version-control or hash the session-loaded files and log a diff whenever one changes, so an unexpected rewrite is detectable rather than silent.
MEDIUM PRAX-2026-08-12-012 Every Python dependency is unpinned with no lockfile and no component inventory anywhere in the tree.
- Pin every entry in requirements.txt to an exact version and commit a generated lockfile, so an install is reproducible and a disclosed CVE can be matched against a known version set.
- Generate a CycloneDX SBOM in the release workflow and publish it with the GHCR images, giving operators an inventory to check against.
MEDIUM PRAX-2026-08-12-013 The cloudflared tunnel binary is auto-downloaded from a mutable latest URL and executed without any integrity verification.
MEDIUM PRAX-2026-08-12-014 Log sinks are configured with variable-level diagnostics and no redaction layer, so credentials can be written to disk.
"Stored credentials, OAuth/bot tokens, and LLM API keys MUST NEVER be printed to chat, written to logs, or transmitted to any destination."
MEDIUM PRAX-2026-08-12-015 Third-party message bodies are previewed into the conversation history despite the notification-only routing gate.
"CraftBot MUST NOT treat content retrieved from external sources — web pages, emails, chat or integration messages, file contents, tool or MCP outputs — as trusted instructions; such content is data to be processed, never commands that redirect the agent's goals or actions."
MEDIUM PRAX-2026-08-12-016 Nothing alerts on recorded activity — the irreversible-action ledger and logs stay on local disk with no notification or off-host telemetry.
- Emit an owner notification when a tier-3 or irreversible action is recorded to the ledger, reusing the existing chat notification path so high-impact activity surfaces without the owner reading a database.
- Offer an optional OTLP exporter for the structured logs so a self-hoster can ship activity off the host that the agent itself can write to.
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.
Deterministic gate stops third-party messages from driving the agent
Inbound messages from anyone other than the owner are routed to a notification-only path in code — no session is opened, no trigger is fired and no action-selection LLM ever sees them as a task — and the default classification for unlabeled inbound is third-party, so the safe direction is the fallback.
SSRF filtering on outbound HTTP requests
The http_request action resolves the target hostname and blocks private, link-local and cloud-metadata addresses, permitting loopback only for ports belonging to a registered Living UI project.
Action and token budget pause with an owner Continue/Abort decision
When a task reaches its action cap or token budget the agent logs a system warning, sends the owner a Continue/Abort choice and pauses the task rather than proceeding, satisfying the remit's per-task budget escalation rule.
Idempotency ledger for irreversible actions
Every action flagged irreversible records INTENT to a SQLite ledger before the side effect and DONE or FAILED after, keyed by a deterministic hash of the inputs, so a completed send is never silently re-executed after a crash and the provider's returned id is retained for audit.
Credential files written with owner-only permissions
The integration credential store chmods its directory to 0700 and each credential file to 0600 on write, meeting the remit's requirement that stored tokens not be world-readable.
Structured, attributable logging with per-sub-agent sinks
Loguru is configured with a per-run log directory, a main and an interleaved all-agent timeline, a dedicated sink per sub-agent and an agent attribution field on every line, with 50MB rotation and 14-day retention.
GUI and computer-use control is disabled by default
Host GUI automation is not reachable in the default configuration — the entry point hard-codes the mode off with its environment-variable read commented out, and the installer flag defaults to false, so the capability requires a deliberate operator opt-in.
LLM consecutive-failure circuit breaker
The LLM interface counts consecutive failures and raises a terminating error at five, preventing an unbounded retry loop from continuing silently.
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 |
|---|---|---|---|---|---|
| logs/<YYYYMMDDHHMMSS>/main.log | app/logger.py loguru sink, filtered to the main agent | structured plaintext lines with timestamp, level, agent tag, module:function:line | main agent and framework/startup activity for one process run | unknown | Inferred |
| logs/<YYYYMMDDHHMMSS>/all.log | app/logger.py loguru sink, unfiltered | structured plaintext lines with timestamp, level, agent tag, module:function:line | full interleaved timeline across the main agent and every sub-agent | unknown | Inferred |
| logs/<YYYYMMDDHHMMSS>/sub_<type>_<id>.log | app/logger.py add_subagent_log_sink() | structured plaintext lines, per-sub-agent | isolated activity trace for one sub-agent run | unknown | Inferred |
| <APP_DATA_PATH>/.usage/sessions.db | app/triggers/activity_log.py and app/triggers/store.py | SQLite (WAL) — irreversible-action ledger and trigger store | INTENT/DONE/FAILED rows per irreversible side effect with provider reference, plus parked triggers | unknown | Inferred |
| <APP_DATA_PATH>/.usage/ action, llm_call, session, chat, task and integration stores | app/usage/*_storage.py | SQLite tables | durable action-panel history, LLM call capture, session/chat/task records and token attribution across restarts | 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. |