# Security

> Security model and command validation

---

- **No shell, ever.** Commands are executed via [`proc_open`](https://www.php.net/manual/en/function.proc-open.php) with an [argv array](https://www.php.net/manual/en/reserved.variables.argv.php).
- **Command whitelist.** [Module scripts](./cli-scripts) must match `modules_v4/<module>/cli/<script>.php`, exist, and resolve inside a `modules_v4/<module>/cli/` directory. Core commands are limited to the read-only/export allowlist.
- **Argument validation.** Every argument token must be a plain option or value — no shell metacharacters, no spaces inside tokens, max 10 tokens.
- **Working directories.** Module scripts run from the webtrees root; core commands run from `data/`.
- **No sensitive data in stdout.** The [tick](./tick) echoes summary lines only; full output stays in the admin-only run history.
- **UI actions** are all named `*Admin*` (admin-only, [CSRF](https://developer.mozilla.org/en/docs/Web/Security/Attacks/CSRF)-protected).
- **Kill switch:** disable the module — the tick then does nothing.
- The tick is single-instance (flock), and a job's child process is hard-killed after its timeout ([`SIGKILL`](https://en.wikipedia.org/wiki/SIGKILL)).
- **Event-queue coalescing.** A flood of identical [events](./events) cannot amplify into one job run per queued row.
- **Webhook token header-only.** Send the token exclusively in the `X-Cronjob-Token` header.
