Skip to content

fix(agents): prevent ReDoS in background-session name derivation#90976

Closed
yetval wants to merge 2 commits into
openclaw:mainfrom
yetval:fix/bash-session-name-redos
Closed

fix(agents): prevent ReDoS in background-session name derivation#90976
yetval wants to merge 2 commits into
openclaw:mainfrom
yetval:fix/bash-session-name-redos

Conversation

@yetval

@yetval yetval commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • tokenizeCommand (src/agents/bash-tools.shared.ts) splits a shell command into tokens with the regex /(?:[^\s"']+|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')+/g. The quoted-span alternatives "(?:\\.|[^"])*" / '(?:\\.|[^'])*' are ambiguous: a backslash matches both \\. and [^"], so a quoted span that never closes forces the engine to try every partition of the backslash run. Runtime is exponential in the number of backslashes after an unterminated quote - classic catastrophic backtracking.
  • Reach: the tokenizer feeds deriveSessionName(command), which runs on session.command whenever background bash sessions are listed, labeled, or sent keys (src/agents/bash-tools.process.ts x10, bash-process-references.ts, bash-tools.process-send-keys.ts). session.command is the agent-issued background command string, so a single crafted background command (e.g. via prompt-injected web/repo/inbound content) pins the Node event loop and stalls the whole agent/gateway process - same impact class as the previously fixed interpreter-heuristic hang (ReDoS in analyzeInterpreterHeuristicsFromUnquoted causes gateway 100% CPU hang #61881).
  • Fix: exclude the backslash from the negated character classes ([^"] -> [^"\\], [^'] -> [^'\\]) so a backslash only ever matches the \\. escape alternative. This removes the ambiguity and makes matching linear. It is exactly the form the sibling tokenizers already use (src/agents/sessions/model-registry.ts:219-220, src/agents/tool-display-common.ts:472); tokenizeCommand was the lone instance still on the unsafe pattern. Well-formed and escaped quoted spans tokenize identically to before.

Tests

  • src/agents/bash-tools.shared.test.ts: added a deriveSessionName block - well-formed quoted commands still label as before, backslash-bearing quoted spans still group into one token, and an unterminated quote followed by 50,000 backslashes returns a string in well under 100ms (times out / hangs on the old regex). Existing resolveSandboxWorkdir / readEnvInt cases unchanged and green.
  • Ran the touched file with the repo runner: 10/10 pass. Type-aware oxlint and oxfmt clean on the touched files.

Real behavior proof (required for external PRs)

  • Behavior addressed: a background bash command whose stored session.command contains an unterminated quote followed by a run of backslashes triggers catastrophic regex backtracking in tokenizeCommand via deriveSessionName, hanging the Node event loop (denial of service) every time the session list / labels / send-keys path derives a name. session.command is agent-controlled, so injected content can reach it.

  • Real environment tested: Linux, Node 22.19, exercising the actual production deriveSessionName exported from src/agents/bash-tools.shared.ts through the repo Vitest module loader (no copied regex). The before column ran the unmodified origin/main source of that file; the after column ran the patched file; everything else identical.

  • Exact steps or command run after this patch: called deriveSessionName on a benign command and on node " + "\\".repeat(n) for n = 22,26,28,30,32,34, timing each with process.hrtime.bigint(), over current head c6829198088 with the patch, and (for the before column) over the origin/main source of the same file.

  • Evidence after fix:

    === deriveSessionName timing (AFTER fix, head c6829198088 + patch) ===
    benign  "node server.js --port 8080" : 0.56 ms
    malicious unterminated quote + 22 backslashes : 0.03 ms
    malicious unterminated quote + 26 backslashes : 0.01 ms
    malicious unterminated quote + 28 backslashes : 0.01 ms
    malicious unterminated quote + 30 backslashes : 0.02 ms
    malicious unterminated quote + 32 backslashes : 0.01 ms
    malicious unterminated quote + 34 backslashes : 0.01 ms
    
  • Observed result after fix: timing is flat (~0.01ms) across the malicious inputs and benign labeling is unchanged; the event loop is no longer blocked.

  • What was not tested: a fully wired gateway round-trip that lists a malicious background session through a live channel (the proof drives the exact function that runs on session.command rather than issuing a live agent run); the sibling tokenizers in model-registry.ts / tool-display-common.ts, which already use the safe form and are unchanged.

  • Before evidence: the same script over the unpatched origin/main source of bash-tools.shared.ts:

    === deriveSessionName timing (BEFORE fix, origin/main c6829198088) ===
    benign  "node server.js --port 8080" : 0.59 ms
    malicious unterminated quote + 22 backslashes :   0.71 ms
    malicious unterminated quote + 26 backslashes :   5.01 ms
    malicious unterminated quote + 28 backslashes :  11.57 ms
    malicious unterminated quote + 30 backslashes :  28.42 ms
    malicious unterminated quote + 32 backslashes :  70.98 ms
    malicious unterminated quote + 34 backslashes : 163.79 ms
    

    Time roughly doubles per +2 backslashes, so n in the 40s reaches seconds and n in the 50s reaches minutes of fully-blocked event loop.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: XS proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 6, 2026
@clawsweeper

clawsweeper Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: passed. Reviewed June 7, 2026, 3:28 PM ET / 19:28 UTC.

Summary
The PR changes deriveSessionName command tokenization to avoid catastrophic backtracking on unterminated quoted input and adds regression tests for quoted labels and long backslash runs.

PR surface: Source 0, Tests +24. Total +24 across 2 files.

Reproducibility: yes. The current-main regex at src/agents/bash-tools.shared.ts:294 is reachable from background session name derivation, and a read-only Node timing probe of that exact pattern showed pathological runtime while the patched pattern stayed flat.

Review metrics: none identified.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • [P2] No repair PR is needed because the branch already contains the focused fix and regression test; the automerge lane can proceed through exact-head gates once the stale human-review pause is cleared.

Security
Cleared: The diff narrows a ReDoS-prone regex and adds regression coverage without changing CI, dependencies, secrets, package metadata, or other supply-chain surfaces.

Review details

Best possible solution:

Land the narrow tokenizer fix with its regression tests after the exact-head checks and maintainer automerge gates pass.

Do we have a high-confidence way to reproduce the issue?

Yes. The current-main regex at src/agents/bash-tools.shared.ts:294 is reachable from background session name derivation, and a read-only Node timing probe of that exact pattern showed pathological runtime while the patched pattern stayed flat.

Is this the best way to solve the issue?

Yes. The patch fixes the ambiguous regex at the exact display-label tokenizer, preserves the intended quoted-label behavior with focused tests, and is narrower than replacing this display-only helper with a full shell parser.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 0c33f4e0786e.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides structured terminal timing output comparing before and after behavior through the production deriveSessionName export, with flat after-fix timings and unchanged benign labeling.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • remove rating: 🌊 off-meta tidepool: Current PR rating is rating: 🦞 diamond lobster, so this older rating label is no longer current.

Label justifications:

  • P1: The PR addresses an agent-controlled ReDoS path that can stall the Node event loop during background exec/process workflows.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 🚀 automerge armed: This PR is in ClawSweeper's automerge lane. Sufficient (terminal): The PR body provides structured terminal timing output comparing before and after behavior through the production deriveSessionName export, with flat after-fix timings and unchanged benign labeling.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides structured terminal timing output comparing before and after behavior through the production deriveSessionName export, with flat after-fix timings and unchanged benign labeling.
Evidence reviewed

PR surface:

Source 0, Tests +24. Total +24 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 1 1 0
Tests 1 25 1 +24
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 26 2 +24

What I checked:

Likely related people:

  • Vincent Koc: Current checkout blame and git show point to commit b9d530e29213bc043727e8cb28c243663809ec4d introducing the current background process helper modules, including deriveSessionName and its tokenizer. (role: recent area contributor; confidence: medium; commits: b9d530e29213; files: src/agents/bash-tools.shared.ts, src/agents/bash-tools.process.ts, src/agents/bash-process-references.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P1 High-priority user-facing bug, regression, or broken workflow. labels Jun 6, 2026
@yetval yetval force-pushed the fix/bash-session-name-redos branch from a9754c3 to b025a9d Compare June 7, 2026 04:44
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 7, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 7, 2026
yetval added 2 commits June 7, 2026 07:16
tokenizeCommand split commands with an ambiguous quoted-span regex where a
backslash matched both the escape alternative and the negated class, so an
unterminated quote followed by a run of backslashes caused catastrophic
backtracking. deriveSessionName runs this on agent-controlled session.command
when background sessions are listed/labeled, so a crafted command could hang
the event loop. Exclude the backslash from the negated classes to make matching
linear, matching the sibling tokenizers that already use the safe form.
@yetval yetval force-pushed the fix/bash-session-name-redos branch from b025a9d to 7c55a39 Compare June 7, 2026 07:18
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 7, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 7, 2026
@Takhoffman

Copy link
Copy Markdown
Contributor

@clawsweeper automerge

@clawsweeper

clawsweeper Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🦞🔧
ClawSweeper saw the passing review, but the PR needs another repair pass before merge.

Source: clawsweeper[bot]
Feedback: structured ClawSweeper verdict: pass (sha=7c55a39b4a03f7f9413dbdd4a8d0d7e701fc829f); failed required checks before automerge: Real behavior proof:CANCELLED
Action: repair worker queued. Run: https://github.com/openclaw/clawsweeper/actions/runs/27102493889
Model: gpt-5.5

I will update this PR branch, or open a safe credited replacement, if the repair worker finds a narrow CI fix.

Automerge progress:

  • 2026-06-07 12:28:31 UTC review queued 7c55a39b4a03 (queued)
  • 2026-06-07 19:28:54 UTC review passed 7c55a39b4a03 (structured ClawSweeper verdict: pass (sha=7c55a39b4a03f7f9413dbdd4a8d0d7e701fc8...)

@clawsweeper clawsweeper Bot added clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jun 7, 2026
@clawsweeper clawsweeper Bot added status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane. clawsweeper:human-review Needs maintainer review before ClawSweeper can continue and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 7, 2026
@clawsweeper

clawsweeper Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🦞✅
ClawSweeper is pausing this repair loop for human review.

Source: clawsweeper[bot]
Reason: - [P1] Review did not complete, so no work-lane recommendation was made. (sha=7c55a39b4a03f7f9413dbdd4a8d0d7e701fc829f)

Why human review is needed:
ClawSweeper found a blocker that should be resolved or accepted by a maintainer before the repair or automerge loop continues.

What the maintainer can do as a next step:
If the maintainer accepts the current risk and wants ClawSweeper to continue merge gates, comment @clawsweeper approve. If more work is needed, resolve the blocker first, then comment @clawsweeper automerge to re-review and continue. If automation should stay paused, leave clawsweeper:human-review in place or comment @clawsweeper stop.

I added clawsweeper:human-review and left the final call with a maintainer.

@yetval

yetval commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. clawsweeper:human-review Needs maintainer review before ClawSweeper can continue labels Jun 7, 2026
@clawsweeper

clawsweeper Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper 🐠 reef update

Thanks for the work on this. ClawSweeper opened a replacement PR only because the source branch was not writable from the available bot permissions. branch tides, not contributor blame.

Why replacement: ClawSweeper could not update the source PR branch directly; GitHub did not grant sufficient push rights to the bot for that branch.
Replacement PR: #91233
Why close: this run explicitly closes the superseded source PR after the credited replacement PR is open, so review continues in one place.
Closing this source PR only because source-PR closing was explicitly enabled for this run.
Attribution is preserved in the replacement PR body and release-note trail.
Co-author credit kept:

fish notes: model gpt-5.5, reasoning high; reviewed against 0a38952.

@clawsweeper clawsweeper Bot closed this Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling clawsweeper:automerge Maintainer opted this PR into bounded ClawSweeper-reviewed automerge P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS status: 🚀 automerge armed This PR is in ClawSweeper's automerge lane.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants