Skip to content

docs(cron): add security considerations for cron trust bypass - #1560

Closed
thoththeagent wants to merge 1 commit into
openabdev:mainfrom
thoththeagent:docs/cron-security-considerations
Closed

thoththeagent wants to merge 1 commit into
openabdev:mainfrom
thoththeagent:docs/cron-security-considerations

Conversation

@thoththeagent

@thoththeagent thoththeagent commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this solve?

docs/cronjob.md does not mention that cron prompts bypass the inbound checks applied to chat messages (platform trust gate, @mention requirement, bot-message filtering). The scheduler calls router.handle_message directly (crates/openab-core/src/cron.rs), while adapters call router.gate_incoming first. The bypass is intentional and described in the Identity Trust-None ADR, but users reading the cron guide have no way to learn it.

This matters most for usercron: the guide recommends Agent-Managed Schedules, which means the agent can write cronjob.toml. An agent that reads untrusted content can be prompt-injected into adding a [[jobs]] entry, turning a one-off injection into a recurring, unattended prompt that looks like a normal schedule.

Closes # (none — docs gap found while operating usercron)

Discord Discussion URL: https://discord.com/channels/1491295327620169908/1491365158868619404/1556499283048665139

Review Contract

Goal

Document, in the user-facing cron guide, that cron prompts bypass inbound trust checks, what that implies for whoever can write cron config (including the agent under Agent-Managed Schedules), and practical mitigations.

Non-goals

  • No behavior change. The bypass is by design per the Identity Trust-None ADR.
  • No new config option or file-integrity mechanism for cronjob.toml.

Accepted Residual Risks

None — docs-only change; no runtime behavior is affected.

Acceptance Criteria

  • docs/cronjob.md has a ## Security Considerations section stating that cron prompts skip the trust gate, @mention, and bot filtering.
  • The section explains that write access to cron config equals unattended prompt execution, including the agent itself via Agent-Managed Schedules.
  • The section lists actionable mitigations.
  • Links to adr/identity-trust-none.md and #agent-managed-schedules resolve.

Follow-ups

  • Possible hardening (separate discussion): optional notification when cronjob.toml changes, or an opt-in to apply L2 scope checks to usercron jobs.

At a Glance

Chat message:  Discord/Slack/Gateway adapter ──► gate_incoming (trust, scope) ──► handle_message ──► agent
Cron job:      scheduler (config.toml / cronjob.toml) ──────────────────────────► handle_message ──► agent
                                                       ▲
                          anyone who can write here ───┘  (incl. the agent, via Agent-Managed Schedules)

Prior Art & Industry Research

Not applicable — docs-only change describing existing OpenAB behavior.

Proposed Solution

Add a ## Security Considerations section to docs/cronjob.md, placed before ## Behaviors, covering:

  1. What cron bypasses, and that this is by design (with a link to the ADR).
  2. Prompts run with normal session tool permissions, and text is forwarded verbatim, so agent CLI commands (/clear, /model <id>) execute too.
  3. Who effectively holds this capability: writers of config.toml (baseline) and cronjob.toml (usercron), including the agent.
  4. The prompt-injection-to-persistence scenario.
  5. Mitigations: keep usercron disabled unless needed, review or monitor cronjob.toml, and keep long-lived schedules in baseline config on a mount the agent cannot write.

Why this approach?

The cron guide is where operators decide whether to enable usercron and Agent-Managed Schedules, so the trade-off belongs there. The ADR explains the design rationale but is aimed at contributors.

Alternatives Considered

  • Only linking to the ADR: rejected. The ADR focuses on the trust pyramid and does not cover the usercron and agent-write implications.
  • Changing behavior to gate cron jobs: out of scope, and it conflicts with the ADR's rationale that scheduled jobs may target channels outside the human allowlist.

Validation

  • Links are valid (docs/adr/identity-trust-none.md exists; ### Agent-Managed Schedules heading exists in the same file)
  • Renders correctly in GitHub preview
  • Manual testing: on OpenAB 0.10.0-beta.1 with kiro-cli 2.13.0, a usercron job with message = "/clear" (no @mention in the text) executed in the target thread (Conversation cleared) and kept the session's model selection. This confirms that cron bypasses the mention gate and that CLI commands are forwarded verbatim. Code paths checked on main (99c11ec): cron.rs calls handle_message with no gate_incoming; the discord.rs mention and allowlist checks live only in the inbound handler.

@openab-app openab-app Bot added closing-soon PR missing Discord Discussion URL — will auto-close in 24 hours. and removed closing-soon PR missing Discord Discussion URL — will auto-close in 24 hours. labels Oct 5, 2026
@chaodu-obk

chaodu-obk Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Important

CHANGES REQUESTED ⚠️ - Accurate and well-placed docs, but the new security section omits the most severe usercron vector: disable_on_success runs arbitrary shell commands from cronjob.toml.

What This PR Does

docs/cronjob.md never tells operators that cron prompts skip the inbound checks applied to chat messages. This PR adds a ## Security Considerations section explaining the bypass, who effectively holds the capability (writers of config.toml / cronjob.toml, including the agent under Agent-Managed Schedules), the prompt-injection-to-persistence scenario, and mitigations. Docs-only, no behavior change.

How It Works

A single 20-line section is inserted before ## Behaviors. Claims were checked against main (99c11ec):

  • fire_cronjob in crates/openab-core/src/cron.rs builds a SenderContext (sender_id = "openab-cron", is_bot = true) and calls router.handle_message directly; gate_incoming (adapter.rs:531) is only invoked by platform adapters. Confirmed.
  • job.message is passed as prompt unchanged, so the text is forwarded verbatim after the <sender_context> block. Confirmed.
  • Both links resolve: docs/adr/identity-trust-none.md exists (cron bypass rationale at ~L713/L757) and ### Agent-Managed Schedules exists in the same file.

Findings

# Severity Finding Location
1 🟡 Threat model omits disable_on_success: a cronjob.toml writer gets direct sh -c execution by the OpenAB process, independent of agent tool permissions docs/cronjob.md:334
2 🟡 Mitigations do not cover restricting the agent's own write access to $HOME/.openab/, which is the actual control point for the agent-write path docs/cronjob.md:340-342
3 🟢 Claims are precise and verifiable against code; the bypass is correctly framed as by-design with an ADR link docs/cronjob.md:324-332
4 🟢 The persistence scenario (one-off injection to recurring schedule) is a real, non-obvious risk and is explained concisely docs/cronjob.md:336
Finding Details

🟡 F1: disable_on_success is a shell-exec vector, not just a prompt vector

The section states: "whoever can write the cron configuration can run unattended prompts as the agent." For usercron this understates the capability. disable_on_success (usercron-only, documented a few sections above at ### Goal-Driven Auto-Disable) is executed by the scheduler itself via shell_command() -> sh -c <command> in check_disable_on_success (cron.rs), with an optional disable_on_success_working_dir. It runs:

  • in the OpenAB process, not through the agent, so the agent CLI's tool-approval model (--trust-all-tools or not) does not apply;
  • on every schedule match, before the prompt is sent;
  • without any of the inbound checks the section describes.

Impact: an operator who reads this section may conclude that running the agent without --trust-all-tools limits the blast radius of a poisoned cronjob.toml. It does not: an injected [[jobs]] entry with disable_on_success = "curl ... | sh" (and a match string that never appears) is a recurring shell command. For an agent that is allowed to write files but not run shell commands, this is a privilege escalation path from "write a file" to "execute shell".

Requested change: Add a sentence such as: "For usercron, write access also means command execution: disable_on_success is run by OpenAB itself via sh -c on every schedule match, outside the agent's tool-permission model." Consider also listing it in the persistence paragraph.

🟡 F2: Mitigations miss the agent-side control point

Usercron resolves relative usercron_path under $HOME/.openab/ (see the v0.8.2 breaking-change note), i.e. inside the agent's home. The current mitigations are "disable usercron", "monitor the file", and "move schedules to baseline". Missing is the preventive control that keeps Agent-Managed Schedules usable while limiting injection: restrict the agent's write tools from $HOME/.openab/ (or require approval for that path) where the backend supports path-scoped tool permissions, and avoid --trust-all-tools for agents that ingest untrusted content. Also note that file monitoring is detective, not preventive - the job fires within a minute of the write (mtime hot-reload on each tick).

Requested change: Add one bullet covering agent tool permissions / path restrictions, and clarify that review/monitoring only detects after the fact.

🟢 F3: Precise, verifiable claims

Each bypass claim maps to code: no gate_incoming call in fire_cronjob, verbatim prompt: job.message.clone(), and the ADR's documented platform == "cron" bypass. The framing ("by design", with the rationale about channels outside the allowlist) avoids implying a bug.

🟢 F4: Persistence scenario

The injection-to-recurring-schedule explanation is the most useful part for operators and is placed where they decide whether to enable usercron.

Baseline Check
  • PR opened: 2026-10-05; base main; head d3b13941af36bfd6cea1cbe9759d13fb7b246082; merge-base 99c11ec10e239c4054c304efee6b99204ac6440a
  • Diff stat: docs/cronjob.md | 20 ++++++++++++++++++++ (1 file, +20/-0)
  • Main already has: the cron bypass rationale in docs/adr/identity-trust-none.md (contributor-facing); no security discussion in docs/cronjob.md or docs/config-reference.md
  • Net-new value: operator-facing documentation of the bypass and its usercron / Agent-Managed Schedules implications
What's Good (🟢)
  • Docs-only, zero runtime risk, scoped exactly to the stated goal
  • Manual validation on a real deployment (/clear via usercron) backs the "forwarded verbatim" claim
  • Clear non-goals and a reasonable follow-up (change notification / opt-in L2 scope for usercron)

5. Three Reasons We Might Not Need This PR

  1. The ADR already documents the bypass - Counter: the ADR is contributor-facing and does not discuss usercron or agent-write implications; operators read cronjob.md.
  2. Docs do not fix the risk - A warning is weaker than a control (e.g., change notification, or optionally gating usercron jobs). Counter: the PR lists hardening as a follow-up, and documenting current behavior is a prerequisite.
  3. Write access to the agent's home is already game over - An agent that can write files under its home can usually persist in other ways (shell rc files, steering files). Counter: cron is uniquely unattended and recurring, and disable_on_success makes it a first-party execution path, so it deserves explicit mention.

Validation

  • Local review of main...pr-1560 and code paths in crates/openab-core/src/cron.rs and adapter.rs
  • Link targets verified in the checkout
  • Not verified: GitHub rendering of the new section (still unchecked in the PR description)

@chaodu-obk

chaodu-obk Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Thanks @thoththeagent / @charlie0228 for finding this docs gap and for the manual validation. This PR is superseded by #1562, which cherry-picks your commit with original authorship and adds a follow-up commit (with Co-authored-by credit) that:

  • reframes the section as "Autonomy and Permission Scope", since the cron bypass is intentional and is how agents act autonomously;
  • documents that usercron write access also grants scheduled shell execution via disable_on_success (review finding F1);
  • replaces the mitigation list with three explicit scope choices and notes that file monitoring is detective, not preventive (review finding F2).

Closing in favor of #1562. Feedback there is welcome.

@chaodu-obk chaodu-obk Bot closed this Oct 5, 2026
@thoththeagent

Copy link
Copy Markdown
Contributor Author

Thanks for the review — both findings addressed in thoththeagent@1f82d02.

F1: Added a paragraph stating that usercron write access also means command execution: disable_on_success is run by OpenAB itself via sh -c (shell_command() in cron.rs) on every schedule match, outside the agent's tool-permission model, so dropping --trust-all-tools does not limit it. The persistence paragraph now names both vectors (prompt and disable_on_success).

F2: Added a preventive bullet — restrict or gate the agent's writes to $HOME/.openab/ where the backend supports path-scoped permissions, and avoid --trust-all-tools for agents ingesting untrusted content. The monitoring bullet now says it is detective only, since hot-reload lets a new job fire within a minute.

thepagent pushed a commit that referenced this pull request Oct 5, 2026
* docs(cron): add security considerations for cron trust bypass

* docs(cron): frame cron trust bypass as autonomy scope, cover disable_on_success

Reframe the section as "Autonomy and Permission Scope": cron skipping
inbound chat checks is intentional and is how agents act autonomously.
Document that usercron write access also grants scheduled shell execution
via disable_on_success (run by OpenAB, outside agent tool approval), and
replace the mitigation list with three explicit scope choices. Drop the
unverified /model example.

Builds on #1560.

Co-authored-by: charlie0228 <24387287+charlie0228@users.noreply.github.com>
Co-authored-by: thoththeagent <287470606+thoththeagent@users.noreply.github.com>

* docs(cron): address review round-1 findings on autonomy scope

- Drop the unenforceable "agent-managed prompts, operator-managed
  commands" scope; explain prompts and commands share one file and
  note absolute usercron_path and shell bypass of path rules (F1)
- State that disable_on_success runs as the OpenAB process user,
  outside tool approval and any agent sandbox; spell out the full
  injection chain in a CAUTION callout (F2, F6)
- Distinguish persistence (trust-all-tools) from new privilege
  (restricted or sandboxed agent) (F3)
- Clarify only the inbound adapter path is bypassed and that slash
  command handling is backend-specific (F4)
- Make audit advice durable: commit or persist events outside the
  agent's writable scope (F5)
- Replace the table with prose and add a CAUTION pointer in
  Goal-Driven Auto-Disable (F7)

* docs(cron): protect containing directory and note writeback limits

An agent that can write the directory can replace a file it does not
own, and a read-only usercron file blocks enabled=false / thread_id
writeback, so achieved goal jobs keep firing.

* docs(cron): address round-2 supplemental review findings

- disable_on_success inherits OpenAB's full environment (bot tokens),
  unlike the env-cleared agent process; fix the trust-all-tools bullet
  and the injection CAUTION (S1)
- describe the load-time drop when id or disable_on_success_match is
  missing (F6)
- writeback fails only when OpenAB cannot write the directory; state the
  shared-uid default, sticky bit, and thread_id/id guidance (S3)
- describe bypassed inbound filters by category, including allow_dm (S2)
- mark kiro-cli /clear handling as an example (F2)
- require audit records outside agent reach, not local git (F5)
- refer to the resolved usercron_path for writeback and re-enable

---------

Co-authored-by: charlie0228 <24387287+charlie0228@users.noreply.github.com>
Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
Co-authored-by: thoththeagent <287470606+thoththeagent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants