Surfaced by review of #105 (head 70bc6b6). Three independent reviewer findings describe the same shape: bin/agent-coord hand-rolls the same logic in more than one place, so a future fix has to be applied twice or thrice to stay correct. None of these are defects today — they are divergence risks in a file whose whole purpose is a security boundary, which is exactly where silent drift is most expensive.
Deliberately excluded from #105: that PR is already a ~2400-line security-sensitive change tripping the autonomous-merge changed-lines-limit, and pure refactors of pre-existing code would widen the blast radius of a diff that needs careful human review. Filed here so the cleanup is not lost.
1. validate_resolved_directory_chain! duplicates validate_secure_directory_chain!s component walk
#105 (comment)
Both split an expanded path into components, walk from /, and call validate_secure_directory_component! per component with the same leaf computation. The only real difference is that validate_secure_directory_chain! also handles symlink resolution/creation and calls into validate_resolved_directory_chain! after resolving. Since the latter is only ever invoked from inside the former, the walk-and-check loop could be one shared helper parameterized by "handle symlinks or not", instead of two near-identical ~15-line copies. A future change to how leaf is computed would need applying twice — precisely the TOCTOU-prone divergence this code is hardened against elsewhere.
2. validate_config_api_url! duplicates HttpStore#parse_base_urls URL policy
#105 (comment)
Both implement: scheme must be http/https with a non-empty host; no query or fragment; plain http rejected unless the hostname is in LOOPBACK_HOSTS. validate_config_api_url! even reaches into HttpStore::LOOPBACK_HOSTS to share the constant, but reimplements the surrounding logic and uses a different error type (Error vs OperationalError). If the loopback/https policy changes in one place — say a new trusted loopback alias — nothing signals that the other needs the same update, so agent-coord config set --api-url could accept a URL the HTTP backend later rejects at request time, or vice versa.
Note this already bit once: #105 needed a separate "Fix IPv6 loopback URL validation" commit (25121dc8) touching both sides.
3. Three reimplementations of the same cli > process_env > user_config precedence chain
#105 (comment)
effective_config_value, the effective_backend_env_value / process_selects_backend? / backend_env_source group, and effective_policy each resolve the same tiered precedence differently. effective_config_value treats an empty string as unset via .to_s.empty?; effective_backend_env_value returns raw values including empty strings, forcing callers like resolve_backend_env to separately normalize (env_api = nil if env_api == ""); effective_policy folds a fourth policy_file tier inline rather than composing. A precedence or empty-string change made in one is not guaranteed to be mirrored in the other two.
The empty-string inconsistency here is not hypothetical — it is the root of the --state-root "" and config show --backend "" reports also raised against #105.
Proposed fix
Factor each cluster into one shared implementation. Best done as a standalone refactor PR with no behavior change, so the diff is reviewable as pure restructuring and the existing suite acts as the regression net.
Surfaced by review of #105 (head
70bc6b6). Three independent reviewer findings describe the same shape:bin/agent-coordhand-rolls the same logic in more than one place, so a future fix has to be applied twice or thrice to stay correct. None of these are defects today — they are divergence risks in a file whose whole purpose is a security boundary, which is exactly where silent drift is most expensive.Deliberately excluded from #105: that PR is already a ~2400-line security-sensitive change tripping the autonomous-merge
changed-lines-limit, and pure refactors of pre-existing code would widen the blast radius of a diff that needs careful human review. Filed here so the cleanup is not lost.1.
validate_resolved_directory_chain!duplicatesvalidate_secure_directory_chain!s component walk#105 (comment)
Both split an expanded path into components, walk from
/, and callvalidate_secure_directory_component!per component with the sameleafcomputation. The only real difference is thatvalidate_secure_directory_chain!also handles symlink resolution/creation and calls intovalidate_resolved_directory_chain!after resolving. Since the latter is only ever invoked from inside the former, the walk-and-check loop could be one shared helper parameterized by "handle symlinks or not", instead of two near-identical ~15-line copies. A future change to howleafis computed would need applying twice — precisely the TOCTOU-prone divergence this code is hardened against elsewhere.2.
validate_config_api_url!duplicatesHttpStore#parse_base_urls URL policy#105 (comment)
Both implement: scheme must be http/https with a non-empty host; no query or fragment; plain
httprejected unless the hostname is inLOOPBACK_HOSTS.validate_config_api_url!even reaches intoHttpStore::LOOPBACK_HOSTSto share the constant, but reimplements the surrounding logic and uses a different error type (ErrorvsOperationalError). If the loopback/https policy changes in one place — say a new trusted loopback alias — nothing signals that the other needs the same update, soagent-coord config set --api-urlcould accept a URL the HTTP backend later rejects at request time, or vice versa.Note this already bit once: #105 needed a separate "Fix IPv6 loopback URL validation" commit (
25121dc8) touching both sides.3. Three reimplementations of the same cli > process_env > user_config precedence chain
#105 (comment)
effective_config_value, theeffective_backend_env_value/process_selects_backend?/backend_env_sourcegroup, andeffective_policyeach resolve the same tiered precedence differently.effective_config_valuetreats an empty string as unset via.to_s.empty?;effective_backend_env_valuereturns raw values including empty strings, forcing callers likeresolve_backend_envto separately normalize (env_api = nil if env_api == "");effective_policyfolds a fourthpolicy_filetier inline rather than composing. A precedence or empty-string change made in one is not guaranteed to be mirrored in the other two.The empty-string inconsistency here is not hypothetical — it is the root of the
--state-root ""andconfig show --backend ""reports also raised against #105.Proposed fix
Factor each cluster into one shared implementation. Best done as a standalone refactor PR with no behavior change, so the diff is reviewable as pure restructuring and the existing suite acts as the regression net.