Skip to content

docs(cron): document cron autonomy and permission scope - #1562

Merged
thepagent merged 5 commits into
mainfrom
docs/cron-security-considerations-v2
Oct 5, 2026
Merged

thepagent merged 5 commits into
mainfrom
docs/cron-security-considerations-v2

Conversation

@chaodu-obk

@chaodu-obk chaodu-obk Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this solve?

docs/cronjob.md does not tell operators that cron prompts skip the inbound checks applied to chat messages (trust gate, @mention, bot filtering), or what write access to the cron configuration grants. This is intentional - cron is how OpenAB gives agents autonomy - but operators need to know the scope to decide how much of it to grant. In particular, usercron write access also grants scheduled shell execution through disable_on_success, which OpenAB runs itself outside the agent's tool-approval model.

Supersedes #1560. Credits: the original section, analysis, and manual validation are by @thoththeagent / @charlie0228 (commit 22e1aae4 is cherry-picked with original authorship; the follow-up commit carries Co-authored-by trailers).

Closes # (none - docs gap)

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 chat checks by design, what write access to config.toml / cronjob.toml grants (including disable_on_success shell execution), and how to choose an autonomy scope.

Non-goals

  • No behavior change. The bypass is by design per the Identity Trust-None ADR, and agent autonomy via cron is intended.
  • 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 ## Autonomy and Permission Scope section stating that cron prompts skip the adapter's inbound filters (allowlists, DM gating, @mention, bot filtering), framed as intentional.
  • The section states that usercron write access also grants shell execution via disable_on_success, run by OpenAB outside the agent's tool approval and with OpenAB's full environment (unlike the env-cleared agent process).
  • The section offers two enforceable scope choices (full autonomy / operator-managed only) and explains that write access cannot be split by field.
  • Links to adr/identity-trust-none.md, #agent-managed-schedules, and #goal-driven-auto-disable 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.
  • Enforcement boundary: an operator-controlled [cron] usercron_allow_commands switch or allowlist in config.toml, or moving disable_on_success to operator-owned config (note the default-compatibility trade-off).
  • Code: remove the unreachable "without disable_on_success_match" fallback in fire_cronjob; consider surfacing load-time usercron drops (missing id/marker, unconfigured platform) beyond a log warning.

At a Glance

Chat message: adapter --> gate_incoming (trust, scope) --> handle_message --> agent (env cleared)
Cron job:     scheduler (config.toml / cronjob.toml) ---> handle_message --> agent (env cleared)
                         |
                         +--> disable_on_success: sh -c (usercron only, run by OpenAB, full env)
              writers: operator (config.toml); operator or agent (cronjob.toml)

Prior Art & Industry Research

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

Proposed Solution

Starting from #1560, rename the section to ## Autonomy and Permission Scope and restructure it into:

  1. Cron prompts skip the adapter's inbound filters (by design), with the ADR link.
  2. What write access to cronjob.toml grants: disable_on_success (sh -c on Unix, cmd /C on Windows) is executed by OpenAB on every schedule match before the prompt, as the OpenAB process user with its full environment; the load-time requirements (id, disable_on_success_match); and when this adds persistence vs. new privilege or credentials.
  3. The prompt-injection-to-recurring-job CAUTION.
  4. Two scope choices (full autonomy / operator-managed only), the shared-uid and writeback caveats for protected files, plus an audit-trail note (records must live outside the agent's reach; detective, not preventive; hot-reload takes effect within about a minute).

The unverified /model <id> example was dropped; /clear was verified in #1560.

Why this approach?

Framing the section around autonomy scope matches the design intent (cron exists to let agents act on their own) while still giving operators the facts. The original "Security Considerations" framing undersold the capability (prompts only) and read as a vulnerability disclosure for intended behavior.

Alternatives Considered

  • Merge docs(cron): add security considerations for cron trust bypass #1560 as-is: rejected; it omits disable_on_success, so operators restricting the agent's shell tool would underestimate what cronjob.toml write access grants.
  • Change behavior to gate cron jobs: out of scope; conflicts with the ADR rationale.
  • A middle "agent-managed prompts, operator-managed commands" scope: dropped; prompts and commands share one file, so it cannot be enforced.

Validation

  • Code paths checked on main (99c11ec): fire_cronjob calls router.handle_message with no gate_incoming; prompt: job.message.clone() is forwarded verbatim; check_disable_on_success runs shell_command() (sh -c / cmd /C) without clearing the environment, while the agent process is started with env_clear(); usercron is hot-reloaded on mtime change each scheduler tick; load_usercron_file drops disable_on_success entries missing id or disable_on_success_match; writeback uses a temp file + rename in the same directory.
  • Anchors exist: ### Agent-Managed Schedules, ### Goal-Driven Auto-Disable, #### Minimal config.toml example; docs/adr/identity-trust-none.md exists.
  • Manual testing (from docs(cron): add security considerations for cron trust bypass #1560): a usercron job with message = "/clear" executed without @mention on OpenAB 0.10.0-beta.1.
  • Renders correctly in GitHub preview

charlie0228 and others added 2 commits October 5, 2026 03:16
…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>
@thoththeagent

Copy link
Copy Markdown
Contributor

Thanks for picking this up and keeping the authorship intact — much appreciated.

The "Autonomy and Permission Scope" framing is better than mine. Presenting the bypass as a scope you choose to grant, rather than as a warning, fits how the feature is meant to be used. The baseline-vs-usercron table makes the disable_on_success point clearer than the paragraph I'd added.

A few notes:

  • My follow-up comment on docs(cron): add security considerations for cron trust bypass #1560 crossed with the close by about 30 seconds. It covered F1/F2 in a similar direction, so there's nothing to reconcile.
  • Dropping the /model example is fair. Only /clear was verified end-to-end through OpenAB; /model was tested directly against kiro-cli acp.
  • I re-read the new section against main. The sh -c / cmd /C behavior in shell_command(), the pre-prompt ordering in fire_cronjob, and the anchors all check out. LGTM from my side.

Related: #1561 (thread_id/id interaction and the thread_id = "" examples) also touches docs/cronjob.md, in the config table and the Behaviors bullet. The hunks don't overlap with this one.

@chaodu-obk

This comment has been minimized.

- 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)
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.
@chaodu-obk

This comment has been minimized.

- 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
@chaodu-obk

chaodu-obk Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Note

LGTM ✅ - The supplemental round-2 findings are addressed in 7accd5f4, every new claim was verified against the code, and no review threads remain open.

What This PR Does

Adds ## Autonomy and Permission Scope to docs/cronjob.md. The section explains that cron prompts skip inbound chat checks by design, and that write access to the usercron file also grants scheduled shell execution through disable_on_success. OpenAB runs that command itself, outside agent tool approval and with OpenAB's full environment. The section then helps operators choose an enforceable autonomy scope. Docs-only; supersedes #1560 and keeps its original authorship.

How It Works

Round 3 reviews head 7accd5f4. Commit history:

  • d2ef9d24 / 49517de4: round-1 findings F1-F7 and the round-2 writeback finding N1, as summarized in round 2.
  • 7accd5f4: supplemental findings that arrived after round 2 was published (S1-S3, plus refinements to the earlier F2/F5/F6 wording), listed below.

Findings

# Severity Finding Location
1 🟢 S1 resolved: disable_on_success inherits OpenAB's full environment (for example platform bot tokens), unlike the env-cleared agent process. The --trust-all-tools bullet no longer says "persistence, not new privilege", and the CAUTION callout explains the bot-token reach beyond the inbound allowlist docs/cronjob.md:348-356
2 🟢 S2 resolved: bypassed inbound filters are described by category (user/channel/role allowlists, allow_dm, mention requirements, bot filtering) instead of a single trust-gate bullet docs/cronjob.md:333-338
3 🟢 S3 resolved: writeback fails only when OpenAB cannot write the directory. The text states the shared-uid default, the directory-ownership and sticky-bit requirements, and the id / thread_id guidance to avoid a new thread on every run docs/cronjob.md:363-369
4 🟢 Load-time drop documented: a usercron entry that sets disable_on_success without a non-empty id and disable_on_success_match is skipped with a warning docs/cronjob.md:346
5 🟢 Audit advice hardened: a local git repo is not enough; records must go to a protected remote or an external/append-only store docs/cronjob.md:371
6 🟢 Writeback and re-enable steps now refer to the resolved usercron_path instead of the hard-coded default path docs/cronjob.md:296, :306
7 🟢 All earlier findings (F1-F7, N1) remain resolved; git diff --check is clean and all anchors resolve docs/cronjob.md:301-371
Finding Details

🟢 Verification on head 7accd5f4

  • Environment split (S1): the agent process calls cmd.env_clear() and passes through only baseline variables plus [agent].env / inherit_env (crates/openab-core/src/acp/connection.rs:393-395). shell_command (crates/openab-core/src/cron.rs:871-883) does not clear the environment, so disable_on_success sees everything OpenAB has, including bot tokens.
  • Load-time validation: load_usercron_file skips entries whose non-empty disable_on_success lacks id or disable_on_success_match (cron.rs:348-360). This makes "on every schedule match" accurate for every job that loads.
  • Thread persistence: should_create_cron_thread creates a thread when thread_id is unset (cron.rs:249-251). The new ID is persisted through update_usercron_job only when id is set (cron.rs:686-695), and that write goes through a temp file and rename in the same directory (cron.rs:917-920). This supports the guidance to set a real thread_id or omit id.
  • Inbound bypass: fire_cronjob dispatches via router.handle_message with no gate_incoming; allow_dm is an L2 scope control in trust.rs.
  • Anchors resolve: #minimal-configtoml-example, #autonomy-and-permission-scope, #goal-driven-auto-disable, #agent-managed-schedules, adr/identity-trust-none.md.

Review Threads

There are no inline review threads or submitted reviews on this PR, so none needed a reply or resolution. Every finding from all rounds is tracked in this comment.

Addressing Reviewer Feedback

External Reviewer A (original #1560 author)

The "Autonomy and Permission Scope" framing is better than mine ... LGTM from my side.

ℹ️ Acknowledged. The framing is kept. Your verification of shell_command(), the pre-prompt ordering, and the anchors still holds on 7accd5f4. /clear is described as an example of backend-specific handling, consistent with your note that /model was tested only directly against the agent CLI.

Related: #1561 also touches docs/cronjob.md ... The hunks don't overlap with this one.

ℹ️ Accepted. 7accd5f4 also edits lines 296 and 306 (writeback and re-enable wording). These are still outside the config-table and Behaviors hunks of #1561. Whichever PR merges second should rebase and re-check.

Internal review rounds

Round Findings Resolution
Round 1 (d34039a1) F1-F7 ✅ Addressed in d2ef9d24
Round 2 (49517de4) N1 ✅ Addressed in 49517de4
Round 2 supplemental S1-S3, plus F2/F5/F6 refinements ✅ Addressed in 7accd5f4
Baseline Check
  • PR opened: 2026-10-05; base main; head 7accd5f45d7b2bade39f12a9b90b3689c6fa7211; merge-base 99c11ec10e239c4054c304efee6b99204ac6440a
  • Diff vs base: docs/cronjob.md only, 5 commits
  • Main already has: the cron bypass rationale in docs/adr/identity-trust-none.md; disable_on_success field docs in docs/cronjob.md and docs/config-reference.md
  • Net-new value: an operator-facing statement that usercron write access includes scheduled shell execution as the OpenAB process user, with OpenAB's environment and outside agent tool approval, plus an enforceable scope choice and the writeback caveats
Suggested Follow-ups (non-blocking)
  • An operator-controlled switch such as [cron] usercron_allow_commands = false (default), or an allowlist for disable_on_success, stored in config.toml.
  • Run disable_on_success with a cleared or minimal environment, matching the agent process, so that bot tokens are not exposed to cron commands.
  • Make the cron trust-gate bypass explicit (the ADR's InboundSource::SystemCron).

5. Three Reasons We Might Not Need This PR

  1. The better fix is in code: clearing the environment for disable_on_success and adding an operator allowlist would remove most of this guidance.
  2. Section length: the writeback and thread caveats make the operator-managed option long. It is accurate, but it is a lot to read for a niche configuration.
  3. Staleness risk: the ADR's Trust Gate migration and any env-handling change will require this section to be revised.

@thepagent
thepagent merged commit 6431a1b into main Oct 5, 2026
2 of 7 checks passed
thepagent pushed a commit that referenced this pull request Oct 23, 2026
…ndow (#1573)

GitHub delivers the hourly schedule every ~4-5h, so the 'commit in the
last ~70min' gate skipped most merges. The last full pre-beta build was
99c11ec (2026-10-03); #1562, #1564, #1568 and #1569 never reached
pre-beta-*, which also kept downstream openab-pty images on Node 22.23.3.

Build when main HEAD differs from the head SHA of the last successful
run that tagged every agent.

Co-authored-by: chaodu-agent <274062505+chaodu-agent@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.

4 participants