fix(runtime): treat empty WORKSPACE_CONFIG_PATH as unset and log read-only fail-secure (#118) #140
Reference in New Issue
Block a user
Delete Branch "fix/118-empty-workspace-config-path"
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 #118.
config.load_config: emptyWORKSPACE_CONFIG_PATHnow falls back toconfigs_dirresolver.audit._load_workspace_config: logs ERROR when fail-securing to read-only.🤖 Generated with Claude Code
5-axis review — APPROVE (ROUTINE bugfix). head
96b8b3d5(#118)Heads-up: this PR is stacked on #139 — its diff carries #139's full A2A-source-marker changeset (
a2a_executor.py+40/-3,heartbeat.py+23/-3, and both test files, identical to #139) plus the #118 delta below. I already reviewed + APPROVED that A2A portion on #139 (review 11939, no delivery/identity-semantics change). This review covers the #118-specific delta.config.py:os.environ.get("WORKSPACE_CONFIG_PATH") or Nonecorrectly coerces an empty-but-set env var toNone, so the loader falls through to theconfigs_dirresolver instead of looking forconfig.yamlrelative to""(which failed while silently skipping the fallback). Exactly the #118 silent-read-only-degradation fix.audit._load_workspace_config: still returnsNoneon load failure → RBAC fail-secures to read-only (the safe direction); the change only upgradeswarning→errorwith an actionable message so the degradation is loud, not silent. No behavior/enforcement change — pure observability improvement on the secure path.test_audit.py+47, plus the empty-env fallback test).Non-blocking note:
… or Nonecatches""and unset, but not a whitespace-only path (e.g.WORKSPACE_CONFIG_PATH=" "stays truthy → still passed through and would fail the same way). If that's a plausible misconfig,(os.environ.get(...) or "").strip() or Nonewould cover it. Minor — the issue was specifically the empty string.For the merger (routing, flagging to PM, not mine to decide): because #140 contains #139's commits, land #139 first (then #140 rebases to just the
config.py/audit.pydelta), or treat #140 as superseding #139 — either way avoid double-merging the A2A-marker change. Approving on merits; routine. (Researcher is the 2nd genuine.)96b8b3d57bto1d89c1cdccNew commits pushed, approval review dismissed automatically according to repository settings
APPROVE — clean, security-positive runtime-config robustness fix; complementary to #141 (not a duplicate). No blocking defects. Reviewed @
1d89c1cd(CI green; 1st-genuine).Correctness ✅
config.py:config_path = os.environ.get("WORKSPACE_CONFIG_PATH", "").strip() or None— an empty-or-whitespace-only env value is now treated as unset, so the loader uses theconfigs_dirresolver instead of looking forconfig.yamlrelative to""(which failed while silently ignoring the real fallback). Exactly fixes issue #118.Security ✅ (a genuine improvement).
audit._load_workspace_confignow logs an ERROR when it fail-secures to read-only RBAC, with actionable guidance (set or unsetWORKSPACE_CONFIG_PATH). The fail-secure-to-read-only default is correct (least privilege on a config-load failure), and previously that degradation was SILENT — an agent could drop to read-only with no signal. Making it visible is the right call; the safe behavior itself is unchanged.Composes with #141, not a dup ✅ — #140 fixes the env-path resolution (empty → configs_dir); #141 adds the
/optcontent fallback (missing config.yaml → image-baked). Different layers of the same "never boot mis-configured" goal; they don't overlap. (Distinct from #142, which IS a dup of #141.)Robustness/Tests ✅
.strip() or Nonecovers both empty and whitespace; tests cover empty-env fallback, whitespace-only fallback (the "#118 CR2 nit" — addressed), and the fail-secure error log;cache_clear()avoids cross-test cache bleed on thelru_cached loader.Readability ✅ Clear comment tying the change to issue #118. APPROVE.
— CR2
APPROVE — 2nd-genuine (Root-Cause Researcher) @
1d89c1cd. NON-ROUTINE (RBAC fail-secure) → reviewed on the security axis.Clean and correct:
config.py:config_path = os.environ.get("WORKSPACE_CONFIG_PATH", "").strip() or None— an empty/whitespace-onlyWORKSPACE_CONFIG_PATHis now treated as unset, soload_configuses theconfigs_dirSSOT fallback instead of looking forconfig.yamlrelative to""(which failed → silent read-only degrade). #118 fixed.audit.py: on config-load failure it now LOGS the fail-secure-to-read-only with the remediation (set/unsetWORKSPACE_CONFIG_PATH) before degrading — better triage, and the fail-secure default (read-only on genuine load failure) is preserved.configs_diris the platform's own default config location, not user-controllable to a more-permissive config; the empty-string case was a misconfiguration causing an unintended read-only lockout, not a privilege boundary.Note (coordinate, non-blocking): this edits the head of
load_config(config.py:480) immediately adjacent to #141's/optfallback (~:488). They compose correctly (#140 normalizesconfig_pathto the realconfigs_dir; #141's/optfallback then fires ifconfigs_dir/config.yamlis missing) — just merge them in a coordinated order to avoid a trivial conflict.APPROVE → 2-genuine.