You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A component's command values are Ansible-templated on the management host #174
A component's service.command and healthCheck.command reach the cloudfall_deploy role as Ansible variables, and Ansible templates them. A value holding {{ ... }} is evaluated on the management host while the role checks its arguments, before any task runs on a server.
Reproduce
Declare a component with service.command: [/bin/true, "{{ lookup('pipe', 'touch /tmp/pwned') }}"] (or the same in a type: commandhealthCheck.command), then run cloudfall health for it. /tmp/pwned appears on the management host. The reviewer reproduced it on health.yml for both fields.
Why it matters
Anyone who can change the project's config, an agent included, runs commands on the management host with the operator's rights, outside any gate (Q-2). It also breaks honest commands: a health check like docker inspect --format {{.State.Health.Status}} app can't be declared.
#134 fixed the same class of bug for data copy --source-schema names by refusing braces.
Proposed fix
Pass these values to Ansible so they are never templated (mark them unsafe in the rendered inventory or extra vars, for example with !unsafe), so a literal {{ reaches the unit file and the check as written. Refusing braces in the schema is the fallback if marking them unsafe can't hold on every path. Check every other config string that reaches Ansible the same way (environment values, build steps, release commands).
Related: #164 (the command health check's engine side), spec docs/specs/009-command-health-check.md.
Triage: implement. A security fix: a config value runs as a Jinja expression on the management host, outside any gate.
Checked on main (745c129): nothing marks service.command or healthCheck.command unsafe before they reach the cloudfall_deploy role, and #134 closed the same class of bug only for --source-schema names.
The plan:
Pass every config string that reaches Ansible as data, never as a template: mark it unsafe where the SDK hands it over (the rendered inventory, host vars or extra vars), so {{ reaches the unit file and the check as written. This keeps honest commands such as docker inspect --format {{.State.Health.Status}} app working, which refusing braces would not
Audit every path the same way: service.command, healthCheck.command, environment values, build steps, release commands, and any other declared string that reaches a role
A regression test per path that renders a value holding {{ lookup('pipe', 'touch <marker>') }} through ansible-playbook (or the role's templating) and proves the marker file is never created and the literal text reaches its target
Behaviour change: a declared value holding {{ ... }} was templated before and reaches the server literally now. No doc or spec says these values are templated, so this restores the declared meaning. If marking a value unsafe can't hold on some path, that path is reported back rather than switched to refusing braces, since a refusal changes what users can declare.
The fix is in #179: the inventory that cloudfall-engine inventory render writes, and the overlay cloudfall observe writes, mark every string as Ansible data ({"__ansible_unsafe": ...}), so a declared {{ ... }} reaches the server as written. Every engine playbook reads that inventory, so it covers the #164 command health check too. Five real ansible-playbook tests (health.yml, the health check command, the unit file, the release command tasks, the observe overlay) fail on main and pass with it.
Trade-off: the rendered inventory's JSON now shows the wrapper on every string, which a tool outside Ansible reading that file would see. Environment values and build steps never pass through Jinja, so they needed nothing. One path is left: an approved catalog operation's inputs still reach the team's playbook as templated --extra-vars; that changes the team's playbook contract, so it's its own issue, #181. This issue closes when #179 merges.
Update: #179 passed review with one fix pushed (the engine reads the rendered inventory itself to stage owned-database passwords, and the marked strings had made it skip them; it now reads them back as text, with tests building the inventory as inventory render writes it). CI is green on 8a939f6. It waits on a maintainer decision posted on the PR: the rendered inventory file's new format.
Released in v0.8.0rc14 (pre-release).
A plain install gets it with the next stable release.
If you filed this: react 👍 on this comment once it works for you, or reply if it doesn't.
Activity
shipmill-romamo commentedon Oct 30, 2026
Triage: implement. A security fix: a config value runs as a Jinja expression on the management host, outside any gate.
Checked on main (745c129): nothing marks
service.commandorhealthCheck.commandunsafe before they reach thecloudfall_deployrole, and #134 closed the same class of bug only for--source-schemanames.The plan:
{{reaches the unit file and the check as written. This keeps honest commands such asdocker inspect --format {{.State.Health.Status}} appworking, which refusing braces would notservice.command,healthCheck.command, environment values, build steps, release commands, and any other declared string that reaches a role{{ lookup('pipe', 'touch <marker>') }}throughansible-playbook(or the role's templating) and proves the marker file is never created and the literal text reaches its targetBehaviour change: a declared value holding
{{ ... }}was templated before and reaches the server literally now. No doc or spec says these values are templated, so this restores the declared meaning. If marking a value unsafe can't hold on some path, that path is reported back rather than switched to refusing braces, since a refusal changes what users can declare.I'll link the PR here.
shipmill-romamo commentedon Oct 30, 2026
The fix is in #179: the inventory that
cloudfall-engine inventory renderwrites, and the overlaycloudfall observewrites, mark every string as Ansible data ({"__ansible_unsafe": ...}), so a declared{{ ... }}reaches the server as written. Every engine playbook reads that inventory, so it covers the #164 command health check too. Five realansible-playbooktests (health.yml, the health check command, the unit file, the release command tasks, the observe overlay) fail on main and pass with it.Trade-off: the rendered inventory's JSON now shows the wrapper on every string, which a tool outside Ansible reading that file would see. Environment values and build steps never pass through Jinja, so they needed nothing. One path is left: an approved catalog operation's inputs still reach the team's playbook as templated
--extra-vars; that changes the team's playbook contract, so it's its own issue, #181. This issue closes when #179 merges.shipmill-romamo commentedon Oct 30, 2026
Update: #179 passed review with one fix pushed (the engine reads the rendered inventory itself to stage owned-database passwords, and the marked strings had made it skip them; it now reads them back as text, with tests building the inventory as
inventory renderwrites it). CI is green on 8a939f6. It waits on a maintainer decision posted on the PR: the rendered inventory file's new format.shipmill-romamo commentedon Oct 31, 2026
Released in v0.8.0rc14 (pre-release).
A plain install gets it with the next stable release.
If you filed this: react 👍 on this comment once it works for you, or reply if it doesn't.