v0.5.1 — Layer R on plan-v1 (F50) + F49 ReDoS hardening#13
Merged
Conversation
The agent-authored artifact-written `pattern` was compiled but not screened
for catastrophic backtracking; evalExits runs it with no wall-clock bound, so
a nested unbounded quantifier ((a+)+, (\d*)*, (x+){1,}) could hang the exit
evaluator. LOW self-DoS (agent authors both pattern and body; no arbiter
compromise), but a real hang: measured (a+)+$ NOT finishing on a 33-char body
in 120s.
Fix at the validation gate (before any tokens): plan.js runs a vanilla
hasNestedQuantifier() state-machine scan after the compile check and reds
invalid-value on the exponential class. Skips escaped atoms and character
classes; bounded outer repeats stay legal. Input-bounding rejected as theater
(the blowup needs only tens of chars); a JS regex timeout as disproportionate
(worker_threads/RE2 dep) for a LOW issue.
TDD: 12-bad/17-good detector battery + validator red-case + detail-names-the-
footgun test. 555/555, typecheck + build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
layerRoot was wired only into the legacy steps[] path (interpret.js); the
accepted plan flow (runPlan) silently ignored it, so a plan-shape job could
never emit root-injected and the pre-registered ON-vs-OFF default-flip read
was impossible on the go-forward surface — the blind-instrument class as a
silently-ignored optional param.
Wire it per step (each micro-wheel is the Layer-1 atom): the tee (stage/settle/
discard + onToolResult outcome probe) is mirrored from interpret.js into
mkWorker; executeStep creates one root per step and calls observe + injects the
note in the middle. Two plan-flow-specific calls, both traced not assumed:
- red-set = the exit evaluator's OWN gap (gapKeep '\S', the whole normalized
complaint) — a check's ^-anchored gapKeep does not survive the exit wrapper
(check "x" red: FAILED …) and would degrade to the dangerous writes-only mode.
- the tee is REQUIRED, not just for verbatim: a step rewrites its one target
every attempt, and the cumulative audit dedups by path, so without the tee
the summary stage never fires either.
Native/clipipe excluded (no onToolResult seam; F48 fallback surface).
Still OFF by default (F41). TDD: 5 tests through the real plan flow — summary
fires + reaches the next prompt, a third fixation escalates to verbatim
surfacing the worker's own teed bytes, OFF emits nothing, native inert.
Docs: CHANGELOG, LAYERS ⚠ note, FINDINGS F49→resolved + F50, context.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…over-rejection Second-model review of the F49+F50 diff surfaced two real MEDIUM findings (three others investigated and refuted — no stale-pendingProbe leak, no content leak, faithful tee mirror). Both fixed TDD-first: 1. F50 gap: runPlan's outer close-fix loop (a full ralph loop judged by the REAL close) was still root-less under layerRoot — the same silent-no-op F50 fixes, in the plan flow's likeliest fixation site (full menu, command-judged). Wired with red-set = the close's own gapKeep (raw close output, so the ^-anchor works). New event phase 'fix'; a fix-loop fixation test drives it end-to-end. 2. F49 false-positive class named symmetrically with the false-negative one: anchor-disambiguated repeated-record patterns ((?:^- .+$\n?)+) run linearly but are flagged by shape. Fail-safe direction (never admits an exponential), cost is one redraft. Three REDOS_OVERREJECTED regression tests lock them as accepted limitations so a future "smarter" detector can't turn them into false negatives (the dangerous direction). Detector unchanged. 559/559, typecheck + build clean. Docs: FINDINGS F49/F50, LAYERS, CHANGELOG, context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rdicts Corrects v1.22's premature "steps[] sunset on landing": Layer 2 landed for the GREEN verdict only; plan-v1 locks soft-green/hitl, which still run only on the legacy steps[] path (hitl is the only working draft-PR flow). Records that the green→plan-v1 / soft-green+hitl→legacy split is a build-order artifact, not a design — plan-v1 is the go-forward home for all three verdicts (the agent authors the workflow; legacy is operator-hardcoded scaffolding). Honest sunset gate: legacy retires when plan-v1 admits+implements hitl + soft-green (the verdict-classes rung, post-Layer-3, with the RSI judged-floor caveat for rubric), as a rewrite-deletion parked for hamr's explicit go. Notes F50 moved the last stranded capability (Layer R) onto plan-v1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/code-review medium branch flagged that hasNestedQuantifier missed the redundant-wrapper class — ((a+))+, (?:(a+))*, (((a+)))+, ((\w+))* — the same exponential class as (a+)+ with an extra grouping level. The flat-only scan propagated an inner repeat to the parent only when the group was DIRECTLY re-quantified, so the wrapped forms validated and could still hang the untimed exit evaluator (each measured >8s on ~29 chars). Fix: on a group close, propagate the inner-repeat flag up through the enclosing group when the group's own body already repeats. MONOTONIC — only ever sets quant=true, so it can only ADD rejections (the fail-safe direction) and provably cannot introduce a new false negative (the dangerous one). All 17 REDOS_GOOD and 3 REDOS_OVERREJECTED cases unchanged; +5 wrapper cases in REDOS_BAD. Full suite 564/564, typecheck, build:types clean. Docs: CHANGELOG / PRD v1.23 / bareloop.context.md (adopter contract) / FINDINGS F49 updated to record the wrapper class + the monotonic close. README unchanged (pitch-only; a detector detail is the wrong genre). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lanrun) Record the parked decision: the tee wiring (Finding 6/7 settle logic) is duplicated ~60 lines across the legacy and plan-v1 paths, currently faithful. Not extracted — legacy is a scheduled rewrite-deletion at the verdict-classes rung, so the duplication resolves at that deletion; refactoring code slated for deletion is negative-value and risks reintroducing the blind-instrument class. Accepted cost: a drift window (both files change together) until legacy retires. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Patch release closing two named items before Layer 3, plus a review-caught fix.
layerRoot) wired into the accepted plan-v1 flow (runPlan): one root per EXECUTE step (red-set = the exit evaluator's own gap) and one in the outer close-fix loop (red-set = the close'sgapKeep). Was silently ignored on the shipped path (wired only into legacysteps[]). Still OFF by default (F41); native/clipipe excluded (noonToolResultseam).artifact-writtenpattern. Input-bounding refuted as theater (33-char body hangs); reject is the doctrine-clean fix. Extended this branch after/code-review mediumcaught a false negative: redundant-wrapper forms (((a+))+,(?:(a+))*,(((a+)))+) slipped the flat-only scan and still hang >8s — closed monotonically (propagate the inner repeat up through the wrapper; only ever adds rejections, the fail-safe direction, cannot introduce a new false negative).steps[]sunsets at the verdict-classes rung (post-Layer-3).Parked (documented, not fixed)
interpret.js(legacy) andplanrun.js(plan-v1). Not extracted — legacy is a scheduled rewrite-deletion, so the duplication resolves at that deletion. Accepted drift window recorded in PRD v1.23.Gates (fresh full, no diff-only, no fit-to-pass)
/securityclean (F49 is a hardening; tee honors the secrets law — scrub-at-source, spine carries counts+paths only)/code-review medium branch: 1 finding fixed, PRD addendum v1.2: menu-breadth probe assigned to adaptlearn, findings return here #2 parked🤖 Generated with Claude Code