core.exec
Run a process directly via argv, without a shell — metacharacters are not interpreted.
core.exec runs cmd with args directly via exec(), without `sh -c`: pipes, redirects, glob, and substitutions do not work, and each args element is passed as a separate token. Shell injection is therefore impossible, but the binary name is still TRUSTED-ONLY. By default the step reports changed=true; you can lower it to a no-op with the creates / unless / onlyif guard parameters, and for a read-only probe with the changed_when: false key.
Requirements
- Rootnot required
- Side
soul-side - Collection
soulstack.exec - Category
exec
exec_subprocessStates
core.exec.run — Run cmd with args via exec(), without a shell.
Always by default (the "run" verb).
A guard creates / unless / onlyif that fired held the step at no-op; for a probe set changed_when: false.
Parameters
| Param | Type | Required / default | Description |
|---|---|---|---|
cmd | string | required | Executable name (argv[0], no shell). |
args | list<string> | optional | Arguments argv[1:]. |
cwd | string | optional | Working directory of the process. |
env | map<string> | optional | Additional environment variables (KEY: VALUE). |
creates | string | optional | Idempotency: if the file exists, the step is skipped (changed=false). |
unless | string | optional | Idempotency: shell command; exit=0 → skip. |
onlyif | string | optional | Idempotency: shell command; exit≠0 → skip. |
Example — Read-only probe: argv without a shell, stdout into register
- name: Read kernel release module: core.exec.run register: kernel_release changed_when: false params: cmd: uname args: ["-r"]Example — Guard creates: marker in place → no-op
- name: Initialize data dir once module: core.exec.run params: cmd: /usr/local/bin/app args: ["init", "--data-dir", "/var/lib/app"] creates: /var/lib/app/.initializedOutput
| Field | Type | Description |
|---|---|---|
stdout | string | |
stderr | string | |
exit_code | number |
Notes
- Guard parameters are checked in the order creates → unless → onlyif; the first one that fires skips the command (changed=false, output { skipped: true, reason }).
- A non-zero exit of the main command does not by itself make the step failed — that is decided by failed_when: (for example, grep with exit 1 is normal).
- The typical register: pattern is a read-only probe with changed_when: false and reading register.<name>.stdout in later where: / failed_when: / output:.
Security & defaults
- The argv form without a shell: metacharacters ($, `, |, &, ;, >, *) in cmd/args are not interpreted — the value "x; rm -rf /" in args is a single literal argument, not a command.
- TRUSTED-ONLY for the binary name: cmd (argv[0]) sets which file will be run, args can change the meaning of the operation; values from register.* / soulprint.* / input.* are acceptable only if the Destiny author trusts them.
- The guards unless / onlyif are executed via `sh -c` — their strings fall under the same ban on untrusted interpolation as core.cmd; creates does not use a shell (only a path existence check).
- The module does not declare run_as_root — the command runs with the soul agent process's privileges; creates reduces the side effects of repeats but only checks path existence, not the success of a previous run.