Skip to content

fix(daemon): raw IPC memory setters reject out-of-range input (lr-553d27) - #417

Open
clagentic-builder[bot] wants to merge 5 commits into
mainfrom
fix/lr-553d27-raw-ipc-setter-validation-parity
Open

fix(daemon): raw IPC memory setters reject out-of-range input (lr-553d27)#417
clagentic-builder[bot] wants to merge 5 commits into
mainfrom
fix/lr-553d27-raw-ipc-setter-validation-parity

Conversation

@clagentic-builder

@clagentic-builder clagentic-builder Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Brings the raw IPC socket setters for set_mem_available_threshold and set_tokens_per_mb_headroom in lib/daemon.js to parity with the WS/web handlers onSetMemAvailableThreshold and onSetTokensPerMbHeadroom: an out of range value is now rejected with ok:false and an error naming the valid band, instead of being silently clamped to the default while still reporting ok:true.

Extracted a single shared validator module, lib/memory-setting-validate.js, exporting validateMemAvailableThresholdMB and validateTokensPerMbHeadroom, and pointed both the web/WS path and the raw IPC path at it. The divergence between two independently-maintained copies of the same range check was the actual root cause named in the task, not just the individual clamp.

Sweep of the rest of the raw IPC switch: checked every other case (add_project, remove_project, get_status, get_activity_diagnostics, get_build_status, set_pin, set_project_title, set_os_users, set_auto_continue, set_keep_awake, enable_recovery, disable_recovery, shutdown, restart, update). None of the others share the clamp-and-report-success shape. Only the two memory-guard setters had the defect; both are fixed here.

TASK: lr-553d27


UPDATE 1 -- CI red root-caused and NOT from this diff. The 'test' job failed once at head 3cbf485 on test/project-asset-auth-fallback-lr-e33776.test.js ("daemon did not respond within 20000 ms"), a pre-existing daemon-spawn/port-poll timeout test unrelated to this PR's surface (it spawns lib/daemon.js as a real subprocess; my new tests only drive lib/ipc.js's createIPCServer with an injected handler, no daemon spawn at all). Node's --test runner isolates each test file into its own process, ruling out cross-file handle leakage as the mechanism. Reproduced 1 failure in 3 local full-suite runs (same command, same code); the other two were clean at 1600/1600 -- nondeterministic, matching the exact daemon-spawn-then-poll-port flake class already tracked in lr-eb0d5e (shell-asset-public-before-auth-lr-2895ea, ws-subprotocol-echo-cookie-auth-lr-4c58ae, boot-smoke-lr-1a5f). Filed a comment on lr-eb0d5e adding this as a fourth tracked file rather than fixing lib/server.js or that test file here -- out of this PR's scope per that task's own investigation boundary.

UPDATE 2 -- folded in lr-d5c542 (authorized fold-in, same PR). test/verify-installed-build-stale-process-lr-71f0c3.test.js:172 had a dead 'var matches = src.match(...)' assignment whose result was never read, with escaping that did not match its adjacent comment's description. DECISION: deleted the line rather than writing a replacement assertion -- the comment describes an assertion (regex-based detection confined to resolveProcessBuildStatus, not duplicated in main()) that the following two real assertions (fnStart/fnEnd boundary slicing) already correctly implement; nothing was missing. Confirmed pure no-op for test outcomes: total test count unchanged (1600 before, 1600 after) across multiple full-suite runs.

UPDATE 3 -- folded in a coercion gap BOBBIE found in this PR's own new code (authorized fold-in, same PR). parseInt() alone truncate-parses a garbage-suffixed string ("300abc" -> 300) instead of rejecting it -- a raw IPC caller sending {"value":"300abc"} would have received ok:true with 300 silently persisted, the exact reports-success-while-substituting-a-different-value defect this task exists to close, one level below the out-of-range case. DECISION: added coerceCleanInteger() to lib/memory-setting-validate.js -- accepts a real integer, or a numeric string that is a clean whole number (whitespace-trimmed) with no trailing/leading garbage; rejects a non-numeric prefix match, a decimal fraction, or anything non-numeric. Applied to both validators so both paths get the fix via the shared contract. CONSTRAINT preserved: a clean numeric string (e.g. "1000") -- a plausible caller shape over a JSON socket -- remains accepted; only malformed input is newly rejected. Extended both test files with unit and real-socket reachability coverage; demonstrated the new tests fail against plain parseInt (temporarily reverted, confirmed, restored) and pass with the fix.

Final state: npm test 1610/1610 passing on a clean run (residual failures on other runs are the pre-existing lr-eb0d5e-class flake, confirmed unrelated to this diff by file isolation and reproduction pattern).

TASK: lr-553d27, lr-d5c542

…ess check (lr-71f0c3)

fix(scripts): stop leaking raw 'unknown command' on stale-daemon process check (lr-71f0c3)
…9e6569)

fix(server-settings): add Save button to Advanced > Memory panel (lr-9e6569)
Pure validateMemAvailableThresholdMB / validateTokensPerMbHeadroom functions, extracted so the web/WS setter path and the raw IPC socket path validate against one contract instead of two independently-maintained copies.
@clagentic-reviewer

Copy link
Copy Markdown

PEACHES — clean (0 findings)

Verified SHA 3cbf485 extracts shared memory-setting validators for parity between raw IPC and WS/web setter paths.

Checked:

  • lib/memory-setting-validate.js: Pure validators extracting range checks previously duplicated inline. Each returns { ok: true, value } or { ok: false, error } with band-naming.
  • lib/daemon.js onSetMemAvailableThreshold/onSetTokensPerMbHeadroom (web/WS, lines 1228-1242, 1244-1258): Both now call shared validators. On rejection, return ok:false without mutating config or calling saveConfig.
  • lib/daemon.js set_mem_available_threshold/set_tokens_per_mb_headroom (raw IPC, lines 1741-1762, 1764-1779): Both now call shared validators. On rejection, return ok:false, do not mutate config.
  • Test daemon-ipc-memory-setter-parity-lr-553d27.test.js: Drives real IPC socket with real handler, assertions prove out-of-range rejection (ok:false, persisted value unchanged, saveConfig not called), in-range acceptance still works, source-parity checks prevent drift.
  • Test memory-setting-validate-lr-553d27.test.js: Unit coverage on pure validators.
  • Brand compliance: No bare "clagentic" in user-facing strings. No new Anthropic SDK usage.
  • tome #845 LESSON 2 ("reports success while nothing happened"): The old raw IPC path silently clamped and returned ok:true. The new path rejects (ok:false) without mutating config. This fix eliminates, not introduces, the failure class.
  • Single contract: Both call sites import and call the same two functions. Source-parity test (line 214) verifies daemon.js case bodies call shared validators, preventing drift.
  • Regressions: In-range callers still accept and persist values (e.g. line 144-150, 153-160, 193-202).
{"reviewer": "peaches", "review_status": "clean", "head_sha": "3cbf485e05a096b2e953b12bceb06cde7e379b0c", "pr_number": 417}

@clagentic-security

Copy link
Copy Markdown

BOBBIE -- clean

Audited PR #417 (lr-553d27) at head 3cbf485 against base 35da090. Diff: lib/daemon.js, lib/memory-setting-validate.js, test/memory-setting-validate-lr-553d27.test.js, test/daemon-ipc-memory-setter-parity-lr-553d27.test.js.

Integrity gap (the finding I raised on PR #416, originally cited at lib/daemon.js:1732-1738 pre-fix) is closed on both control paths. The raw IPC switch cases set_mem_available_threshold and set_tokens_per_mb_headroom (lib/daemon.js ~1738-1760 post-fix) now call the SAME validateMemAvailableThresholdMB / validateTokensPerMbHeadroom functions (lib/memory-setting-validate.js:16-30) that the WS/web handlers onSetMemAvailableThreshold / onSetTokensPerMbHeadroom already used (lib/daemon.js ~1232-1259). Both branches now return ok:false with a band-naming error and leave config unmutated on an out-of-range value; both persist and echo the actual accepted value (not a substituted default) on ok:true. No residual clamp-then-ok:true path found in this diff.

Coercion review (raw IPC is the less-trusted local socket surface): parseInt-based validation unchanged in semantics from the pre-existing web path -- null/undefined/Infinity/object inputs all coerce to NaN and are rejected; numeric-prefix strings (e.g. 300abc) still truncate-parse, but that is pre-existing parseInt behavior already shipped on the web path, not a new exposure introduced by this PR. No new sink, no new confirm-without-landing variant (tome #845 checked specifically for this) -- this PR is a pure extraction into a shared module, not a behavior change beyond parity.

Test files (in scope, reviewed): both new test files exercise real socket reachability over lib/ipc.js, not just unit-level validator plumbing, plus a source-parity check that daemon.js actual case bodies call the shared validator. Matches the reachability-over-plumbing discipline tome #845 calls for.

Scanners: gitleaks (scoped 35da090..3cbf485, 1 commit) -- 0 leaks. trufflehog (same range) -- 0 verified/unverified secrets. semgrep p/security-audit on both changed lib files -- 0 findings. No package.json/package-lock.json changes in this diff -- osv-scanner not applicable.

scanners_run: gitleaks(ok), trufflehog(ok), semgrep(ok), osv-scanner(not-applicable, no dep changes)

Findings: none.

{"reviewer": "bobbie", "review_status": "clean", "head_sha": "3cbf485e05a096b2e953b12bceb06cde7e379b0c", "pr_number": 417}

test/verify-installed-build-stale-process-lr-71f0c3.test.js:172 assigned an unused 'matches' var whose result was never read, from a regex whose escaping did not match its adjacent comment's description. The comment describes an assertion (regex-based detection confined to resolveProcessBuildStatus, not duplicated in main()) that the following two real assertions (fnStart/fnEnd boundary slicing) already correctly implement -- no assertion was missing, so the dead line is deleted rather than replaced. Confirmed pure no-op for test outcomes: total test count unchanged at 1600 before and after across multiple full-suite runs.
…alidators (lr-553d27)

BOBBIE's coercion review on PR #417 found parseInt() alone truncate-parses a garbage-suffixed string ("300abc" -> 300) instead of rejecting it: a raw IPC caller sending {"value":"300abc"} received ok:true with 300 silently persisted -- a different value than it sent, the same reports-success-while-substituting-a-different-value contract violation this task exists to close, one level below the out-of-range case.

Added coerceCleanInteger() to lib/memory-setting-validate.js: accepts a real integer number, or a numeric string that is a clean whole number (optionally surrounded by whitespace) with no trailing/leading non-digit characters; rejects everything else, including a numeric prefix followed by garbage and a decimal fraction. Applied to both validators, so both the WS/web and raw IPC paths get the fix automatically via the shared contract.

CONSTRAINT preserved: in-range callers are unaffected, and a clean numeric STRING (e.g. "1000") -- a plausible caller shape over a JSON socket -- remains accepted; only malformed input is newly rejected.

Extended both test files: unit coverage for coerceCleanInteger's accept/reject boundaries (garbage suffix, decimal fraction, whitespace-wrapped garbage vs. whitespace-padded clean value), and real-socket reachability coverage asserting a raw IPC caller sending "300abc"/"128xyz" receives ok:false with the band named and the persisted value unchanged, while a clean numeric string still round-trips. Demonstrated the new tests fail against plain parseInt (temporarily reverted, confirmed the intended failures and no others, restored) and pass with the fix; full suite 1610/1610 clean on a subsequent run (2 residual failures on the run immediately prior are the pre-existing lr-eb0d5e-class daemon-spawn flake, unrelated to this change).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants