fix: feat: task dispatcher — poll ops repo and launch runners (#45) #54
No reviewers
Labels
No labels
action
backlog
blocked
in-progress
priority
tech-debt
underspecified
vision
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: johba/disinto#54
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/issue-45"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #45
Changes
AI Review
Summary\n\nImplements the dispatcher loop: polls a local copy of the ops repo for approved action JSON files and launches a runner container per action. The structure is sound, but there are four functional bugs and one critical security issue that need to be fixed before this can run correctly.\n\n---\n\n### Critical\n\n- critical
dispatcher.sh:87: Secret values leaked in logs.log "Running: ${compose_cmd}"prints the fully-expanded command, which includes-e CLAWHUB_TOKEN=<actual_value>for every vault secret. This violates AD-005 ("never embed secrets in issue bodies, PR descriptions, or comments"). Log only the command skeleton without the values, e.g. redact with=***.\n\n---\n\n### High\n\n- highdispatcher.sh:78-85:-eflags placed after the formula/id arguments.docker compose runsyntax isdocker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]. Options like-e KEY=VALmust come before the service name. As written, the-etokens are appended afterand are passed as container arguments to the entrypoint, not as environment variables. Secrets will never be injected.\n\n- highdispatcher.sh:38: Wrong URL for cloning the ops repo.git clone "${FORGE_WEB}" "${OPS_REPO_ROOT}"usesFORGE_WEB, which (perlib/env.sh:87) resolves to the code repo URL (${FORGE_URL}/${FORGE_REPO}). The log message even says "from ${FORGE_OPS_REPO}" — use${FORGE_OPS_REPO}(set in env.sh to the ops repo slug) for the clone.\n\n- highdispatcher.sh:99: Ops repo never refreshed inside the polling loop.ensure_ops_repo(which doesgit pull) is called once before thewhile trueloop. After startup the local copy is never updated, so new approved actions committed to the remote will never be picked up unless the container is restarted. The pull needs to happen at the top of every loop iteration.\n\n- highdispatcher.sh:88:evalwith ops-repo-controlled input.formulaandidare read from JSON files in the ops repo; if either contains shell metacharacters (spaces, semicolons,$(…)) they can inject arbitrary commands. ShellCheck will flag this as SC2294. Replace with an array-based approach:local -a cmd=(docker compose run --rm ...)and execute with"${cmd[@]}".\n\n---\n\n### Medium\n\n- mediumdispatcher.sh:117: env.sh unsets vault secrets before dispatcher can read them.lib/env.shlines 117–118 explicitly unsetGITHUB_TOKENandCLAWHUB_TOKEN. The dispatcher sources env.sh first, then tries to expand${!secret}for those exact secret names — they will be empty (or causeset -uto abort). The edge container needs to load vault secrets after env.sh, or env.sh must not unset them in the dispatcher context.Verdict
REQUEST_CHANGES — Four functional bugs and one critical security issue: secrets logged in plaintext, wrong clone URL, ops repo never re-polled, and -e flags built in wrong position for docker compose run
Reviewed at
c9ef5eb| AGENTS.mdAI Review: REQUEST_CHANGES — Four functional bugs and one critical security issue: secrets logged in plaintext, wrong clone URL, ops repo never re-polled, and -e flags built in wrong position for docker compose run
AI Re-review (round 2)
Previous Findings\n\n- Secret values leaked in logs → FIXED:
cmd[*]now shows=***for all secrets\n--eflags placed after formula/id → FIXED: array-based build puts flags before the service name\n- Wrong clone URL (FORGE_WEB) → FIXED: now usesFORGE_OPS_REPO\n- Ops repo never refreshed in loop → FIXED:ensure_ops_repomoved insidewhile true\n-evalwith external input → FIXED: replaced with"${cmd[@]}"\n-env.shunsets vault secrets → FIXED:.env.vault.enc/.env.vaultblock loaded afterenv.sh\n\n---\n\n### New Issues\n\n- highdispatcher.sh:96: Secrets injected as literal***, not real values. The fix addresses log redaction correctly, but uses the samecmdarray for both logging and execution. Line 96 doescmd+=(-e "${secret}=***"), so"${cmd[@]}"on line 106 runsdocker compose run -e CLAWHUB_TOKEN=*** .... The runner container receives the literal string***as its token and every vault action will fail with auth errors. The simplest fix is to pass-e SECRET_NAMEwithout a value — Docker/compose will inherit the value from the calling process's environment, which already has the real secrets loaded on lines 27–37.\n\n- mediumdispatcher.sh:52:FORGE_OPS_REPOdefaults to a slug, not a clonable URL.lib/env.sh:108setsFORGE_OPS_REPOto${FORGE_REPO}-ops(e.g.johba/disinto-ops).git clone "johba/disinto-ops"will fail unlessFORGE_OPS_REPOis overridden to a full URL in the container environment. Should use"${FORGE_URL}/${FORGE_OPS_REPO}"to construct a clonable URL from the variables that are already defined.Verdict
REQUEST_CHANGES — One critical regression introduced by the fix: runner container receives literal '***' as secret values instead of real tokens, so all vault actions will fail with auth errors
Reviewed at
649a893| Previous:c9ef5eb| AGENTS.mdAI Re-review (round 2): REQUEST_CHANGES — One critical regression introduced by the fix: runner container receives literal '***' as secret values instead of real tokens, so all vault actions will fail with auth errors
AI Re-review (round 3)
Previous Findings\n\n- Secrets injected as literal
***→ FIXED:cmd+=(-e "${secret}")(no value) lets Docker inherit the real token from the dispatcher's environment; a separatelog_cmdarray redacts the secret name in logs\n-FORGE_OPS_REPOslug not clonable URL → FIXED: clone now uses"${FORGE_URL}/${FORGE_OPS_REPO}"\n\n### Summary\n\nAll findings from rounds 1 and 2 are resolved. The dispatcher correctly loads vault secrets after env.sh, clones the ops repo via a full URL, refreshes it every poll cycle, builds the docker compose command as an array (injection-safe), injects secrets with-e NAMEso Docker inherits real values from the environment, and logs a fully-redacted skeleton.Verdict
APPROVE — All previous findings addressed; dispatcher is correct, secure, and ready to merge
Reviewed at
6be0eee| Previous:649a893| AGENTS.mdAI Re-review (round 3): APPROVE — All previous findings addressed; dispatcher is correct, secure, and ready to merge