ADR-0003: Using eval to pass config from task-setup.js to tide.sh
Date: 2026-03-30 Status: Accepted
Context
tide.sh is a zsh script that needs several values from task.json (command, argument, retries, jitter, etc.). The shell cannot parse JSON natively, so a Node helper (task-setup.js) reads the file and emits shell variable assignments that the shell evaluates:
eval "$(node task-setup.js task.json)"eval has a reputation as a security risk because it executes arbitrary shell code. The concern would be: if a malicious value were injected into task.json, it could be executed by the shell when the variables are evaluated.
task-setup.js does quote all values using single-quote escaping ('...' with '\'' for embedded single quotes), which correctly neutralizes shell metacharacters. However, it is worth documenting why eval is considered acceptable here rather than relying solely on the quoting implementation.
Decision
eval is acceptable for this use case. task.json lives in ~/.tide/tasks/<id>/task.json, a path owned and writable only by the current user. An attacker who can write to that path already has full write access to the user's home directory — at which point they can modify .zshrc, .ssh/authorized_keys, launchd plists, or any other user-owned file to achieve arbitrary code execution through far simpler means. The eval surface adds no meaningful attack vector beyond what already exists.
Alternatives considered:
- Pass values as CLI arguments to tide.sh — launchd's
ProgramArgumentsis static at plist-write time; runtime values likeargumentandmaxRetrieswould still need to be read dynamically. - Source a generated env file — same trust boundary as
eval; sourcing a file the user owns is equivalent. - Rewrite tide.sh in Node — eliminates the shell/Node boundary entirely but loses the simplicity of a thin shell wrapper and complicates process management.
Consequences
- The quoting implementation in
task-setup.jsmust remain correct. Any future fields added to the emitted output must use the sameq()helper. - This decision is scoped to a single-user personal tool. A multi-user or networked deployment would require a different trust model.