{"record":{"id":"0055207fd7e883bd","repo":"tinyhumansai/openhuman","slug":"command-blocked-by-security-policy-cmd","errorCode":null,"errorMessage":"Command blocked by security policy: {cmd}","messagePattern":"Command blocked by security policy: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/cron/ops.rs","lineNumber":115,"sourceCode":"            _ => anyhow::bail!(\"Cannot update expression/tz on a non-cron schedule\"),\n        };\n        Some(Schedule::Cron {\n            expr: expression.unwrap_or(existing_expr),\n            tz: tz.or(existing_tz),\n            active_hours: existing_active,\n        })\n    } else {\n        None\n    };\n\n    if let Some(ref cmd) = command {\n        let security = SecurityPolicy::from_config(\n            &config.autonomy,\n            &config.workspace_dir,\n            &config.action_dir,\n        );\n        if !security.is_command_allowed(cmd) {\n            anyhow::bail!(\"Command blocked by security policy: {cmd}\");\n        }\n    }\n\n    let patch = CronJobPatch {\n        schedule,\n        command,\n        name,\n        ..CronJobPatch::default()\n    };\n\n    update_job(config, id, patch)\n}\n\n/// Parse a human-friendly delay string (e.g. \"5m\", \"2h\", \"30s\") into a\n/// `chrono::Duration`. Defaults to minutes when no unit is given.\npub fn parse_human_delay(input: &str) -> Result<chrono::Duration> {\n    let input = input.trim();\n    if input.is_empty() {","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/cron/ops.rs#L97-L133","documentation":"Before persisting a new command, update_cron_job runs it through SecurityPolicy built from config.autonomy, workspace_dir and action_dir. If is_command_allowed(cmd) is false — the command class is not permitted at the current tier, or the command hits unconditionally forbidden system/credential paths — the update bails without touching the job.","triggerScenarios":"`openhuman cron update <id> --command 'curl ... | sh'` under a readonly/supervised tier; commands whose classify_command result (Network/Install/Destructive, or unrecognized which defaults to Write) is gated by the tier; commands touching always-forbidden directories.","commonSituations":"Hardening autonomy down to readonly and then editing existing cron jobs; CI containers with a strict [autonomy] block; a command that was fine at creation time but policy has since tightened.","solutions":["Choose a command the current tier allows (read-only commands under readonly)","Adjust the autonomy tier / workspace_only / trusted_roots via the config.update_autonomy_settings RPC (Settings → Agent access) and retry","If the command is genuinely required but policy must stay strict, run it from a context outside the policy scope rather than weakening the tier globally"],"exampleFix":"# before\n[autonomy]\ntier = \"readonly\"\n# --command 'rsync ...' is Write-class: blocked\n\n# after\n[autonomy]\ntier = \"supervised\"\n# Write-class commands pass is_command_allowed at this tier","handlingStrategy":"validation","validationCode":"// Mirror the exact guard before attempting the update\nlet security = SecurityPolicy::from_config(&config.autonomy, &config.workspace_dir, &config.action_dir);\nif !security.is_command_allowed(cmd) {\n    return Err(format!(\"'{cmd}' not allowed at the current autonomy tier\"));\n}","typeGuard":null,"tryCatchPattern":"Catch the bail and rekey it to a policy-denied outcome (distinct from store errors) so the UI can offer 'adjust agent access' instead of a generic failure.","preventionTips":["Validate commands against the same SecurityPolicy at authoring time in the UI","Keep cron job commands inside action_dir and within the tier's allowed command classes","Re-audit existing job commands after tightening [autonomy] — edits, not just creation, are gated"],"tags":["cron","security","policy","command"],"backgroundTag":"command-blocked-by-policy","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}