Skip to content

feat(security): support operator-managed network proxy routing#70044

Merged
joshavant merged 13 commits into
mainfrom
feat/ssrf-network-proxy
Apr 28, 2026
Merged

feat(security): support operator-managed network proxy routing#70044
joshavant merged 13 commits into
mainfrom
feat/ssrf-network-proxy

Conversation

@jesse-merhi

@jesse-merhi jesse-merhi commented Apr 22, 2026

Copy link
Copy Markdown
Member

Summary

Adds opt-in network proxy routing for deployments that run their own filtering forward proxy. OpenClaw does not bundle, download, start, configure, or certify a proxy; operators provide the proxy technology that fits their environment and enable routing with proxy.enabled plus proxy.proxyUrl or OPENCLAW_PROXY_URL.

The existing application-level fetchWithSsrFGuard protections remain active. The proxy URL must be an http:// forward proxy listener; HTTPS target requests still work through that proxy via HTTP CONNECT.

Why

A filtering proxy gives operators a central egress-control point for OpenClaw runtime traffic. It can enforce destination policy after DNS resolution and at connect time, improve auditability, and cover ordinary process-local JavaScript HTTP clients that do not call the guarded fetch helper directly. This is useful defense in depth for SSRF and DNS rebinding risks without making OpenClaw own a proxy implementation.

What Changed

  • Adds proxy.enabled and proxy.proxyUrl, with OPENCLAW_PROXY_URL as the URL fallback.
  • Routes process-local HTTP and WebSocket clients through the configured operator-managed proxy when enabled:
    • undici/global fetch() via the global dispatcher
    • node:http / node:https clients via global-agent
    • typical ws clients, including callers that pass explicit agents
  • Rejects https:// proxy URLs because the Node core HTTP routing layer expects HTTP forward proxy listeners; HTTPS destinations still work through an http:// proxy via CONNECT.
  • Clears NO_PROXY, no_proxy, and GLOBAL_AGENT_NO_PROXY while routing is active so loopback/internal destinations cannot bypass the filtering proxy.
  • Preserves a narrow literal-loopback-IP Gateway control-plane path for local RPC traffic.
  • Restores previous proxy environment values and resets undici/global-agent state on normal completion, signals, and hard process exit.
  • Removes bundled proxy build/download/postinstall/release machinery from npm packaging and CI.
  • Adds proxy-agnostic security docs covering benefits, requirements, validation, and recommended blocked destinations.
  • Adds unit and e2e coverage for config validation, lifecycle env injection/restoration, CLI cleanup, service/container env forwarding, and external proxy routing.

Configuration

proxy:
  enabled: true
  proxyUrl: http://127.0.0.1:3128

Or:

openclaw config set proxy.enabled true
OPENCLAW_PROXY_URL=http://127.0.0.1:3128 openclaw gateway run

Startup Behavior

Condition Behavior
proxy.enabled is absent/false Proxy routing does not start
proxy.enabled=true but no valid HTTP proxy URL is configured Protected commands fail startup instead of falling back to direct network access
Config cannot be loaded for a protected command Protected command startup fails before runtime work starts
Help/version and explicit local control-plane commands Skip proxy startup
CLI exits normally or via SIGINT/SIGTERM Restore proxy env and dispatcher state

Reviewer Focus

  • src/infra/net/proxy/proxy-lifecycle.ts: env injection/restoration, undici/global-agent reset behavior, and Gateway loopback bypass scope.
  • src/config/zod-schema.proxy.ts: public config contract and HTTP proxy URL validation.
  • src/cli/run-main.ts: runtime command startup and normal-return/signal cleanup.
  • src/cli/container-target.ts: container proxy URL forwarding guard.
  • src/gateway/client.ts: direct Gateway control-plane transport only where appropriate.
  • docs/security/network-proxy.md: proxy-agnostic benefits, requirements, validation, and denylist guidance.
  • src/infra/net/proxy/external-proxy.e2e.test.ts: external proxy routing coverage without a bundled proxy.

Validation

  • Focused proxy, CLI cleanup, service env, container env, ACP startup, and external-proxy tests pass locally.
  • pnpm build passes locally.
  • CI is green on the current PR head.

@jesse-merhi
jesse-merhi requested a review from a team as a code owner April 22, 2026 07:19
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation cli CLI command changes scripts Repository scripts size: XL maintainer Maintainer-authored PR labels Apr 22, 2026
@greptile-apps

greptile-apps Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a substantial network-level SSRF protection layer by routing all outbound Node.js HTTP traffic through a Caddy forward-proxy sidecar with an IP ACL, correctly closing the DNS-rebinding TOCTOU gap that existed in application-level fetchWithSsrFGuard checks. The dual-stack interception (undici EnvHttpProxyAgent + global-agent monkey-patch) is well-engineered and test coverage is thorough.

  • ::ffff:0:0/96 absent from DEFAULT_BLOCKED_CIDRS: The PR threat model marks IPv4-mapped IPv6 bypass as mitigated, but the CIDR is not in the blocklist — the e2e test passes only because Caddy may normalise these addresses internally, which is not a guaranteed contract.

Confidence Score: 4/5

Safe to merge once the IPv4-mapped IPv6 CIDR gap is resolved; remaining findings are style/robustness.

One P1 security finding: ::ffff:0:0/96 is claimed mitigated in the threat model but absent from the default blocklist, leaving implicit reliance on undocumented Caddy normalisation behaviour. All other findings are P2.

src/infra/net/ssrf-proxy/caddy-config.ts — DEFAULT_BLOCKED_CIDRS needs ::ffff:0:0/96

Security Review

  • Missing IPv4-mapped IPv6 CIDR (::ffff:0:0/96): DEFAULT_BLOCKED_CIDRS in caddy-config.ts does not include ::ffff:0:0/96, despite the PR threat model claiming this vector is mitigated. The e2e tests pass only if Caddy's forwardproxy plugin silently normalises IPv4-mapped addresses; explicit blocking is recommended.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/net/ssrf-proxy/caddy-config.ts
Line: 29-43

Comment:
**IPv4-mapped IPv6 CIDR absent from blocklist**

The PR threat model explicitly claims `::ffff:0:0/96` (IPv4-mapped IPv6) is blocked, and `ipv6.e2e.test.ts` asserts `::ffff:127.0.0.1` is denied. However, this CIDR does not appear in `DEFAULT_BLOCKED_CIDRS`. The e2e test may pass if Caddy's `forwardproxy` plugin internally normalises IPv4-mapped addresses to their IPv4 equivalents before ACL evaluation — but that behaviour is not guaranteed across versions or configurations. Without an explicit blocklist entry, the protection is implicit and fragile. Adding `"::ffff:0:0/96"` makes the intent self-documenting and eliminates reliance on undocumented Caddy normalisation.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/infra/net/ssrf-proxy/proxy-process.ts
Line: 60-82

Comment:
**TOCTOU port race in `pickFreeLocalhostPort`**

The helper binds a probe server to `:0`, records the assigned port, closes the server, then returns the port number. Caddy is spawned after this returns, so there is a window between `server.close()` and Caddy's `bind()` during which another process can claim the same port. Under port-pressure environments (CI, parallel test runs, busy systems) this causes `startCaddyProxy` to fail intermittently. The standard fix is to keep the probe socket open until Caddy is spawned. At minimum, document the limitation in the JSDoc so callers know to retry on `EADDRINUSE`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/infra/net/ssrf-proxy/proxy-process.ts
Line: 158-165

Comment:
**`--adapter ""` may not survive Caddy version changes**

Passing an explicit empty string for `--adapter` relies on Caddy treating `""` identically to "no adapter specified" and falling back to JSON. That behaviour is currently consistent with the Caddy source, but it is not part of the documented CLI contract; a future Caddy version that validates adapter names could break startup silently. The clean invocation for a JSON config is to omit the flag entirely.

```suggestion
    proc = spawn(binaryPath, ["run", "--config", "-"], {
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/cli/run-main.ts
Line: 211-220

Comment:
**SIGINT handler doesn't re-signal the process**

The shutdown closure calls `void stopSsrFProxy(ssrfProxyHandle)` and returns. Because the `process.once("SIGINT")` listener is consumed, the first Ctrl+C only fires the cleanup and leaves the process running until `stopSsrFProxy` resolves or the user presses Ctrl+C again. Whether this matters in practice depends on whether another SIGINT handler elsewhere already handles process exit — worth verifying.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(security): network-level SSRF prote..." | Re-trigger Greptile

Comment thread src/infra/net/ssrf-proxy/caddy-config.ts Outdated
Comment on lines +60 to +82
server.on("error", reject);
});
}

/**
* Resolves the path to the caddy binary.
* Priority: explicit binaryPath option → OPENCLAW_CADDY_BINARY env var → 'caddy' (from PATH).
*/
export function resolveCaddyBinaryPath(binaryPath?: string): string {
if (binaryPath) {
return binaryPath;
}
const envPath = process.env["OPENCLAW_CADDY_BINARY"];
if (typeof envPath === "string" && envPath.trim().length > 0) {
return envPath.trim();
}
return "caddy";
}

/**
* Waits until the Caddy proxy is accepting TCP connections on the given port,
* or throws if the timeout is exceeded or the process exits unexpectedly.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 TOCTOU port race in pickFreeLocalhostPort

The helper binds a probe server to :0, records the assigned port, closes the server, then returns the port number. Caddy is spawned after this returns, so there is a window between server.close() and Caddy's bind() during which another process can claim the same port. Under port-pressure environments (CI, parallel test runs, busy systems) this causes startCaddyProxy to fail intermittently. The standard fix is to keep the probe socket open until Caddy is spawned. At minimum, document the limitation in the JSDoc so callers know to retry on EADDRINUSE.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/net/ssrf-proxy/proxy-process.ts
Line: 60-82

Comment:
**TOCTOU port race in `pickFreeLocalhostPort`**

The helper binds a probe server to `:0`, records the assigned port, closes the server, then returns the port number. Caddy is spawned after this returns, so there is a window between `server.close()` and Caddy's `bind()` during which another process can claim the same port. Under port-pressure environments (CI, parallel test runs, busy systems) this causes `startCaddyProxy` to fail intermittently. The standard fix is to keep the probe socket open until Caddy is spawned. At minimum, document the limitation in the JSDoc so callers know to retry on `EADDRINUSE`.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/infra/net/ssrf-proxy/proxy-process.ts Outdated
Comment thread src/cli/run-main.ts
Comment on lines +211 to +220
}
// Graceful shutdown — stop Caddy when openclaw exits via any signal.
if (ssrfProxyHandle) {
const shutdown = () => {
void stopSsrFProxy(ssrfProxyHandle);
};
process.once("SIGTERM", shutdown);
process.once("SIGINT", shutdown);
process.once("exit", shutdown);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 SIGINT handler doesn't re-signal the process

The shutdown closure calls void stopSsrFProxy(ssrfProxyHandle) and returns. Because the process.once("SIGINT") listener is consumed, the first Ctrl+C only fires the cleanup and leaves the process running until stopSsrFProxy resolves or the user presses Ctrl+C again. Whether this matters in practice depends on whether another SIGINT handler elsewhere already handles process exit — worth verifying.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/run-main.ts
Line: 211-220

Comment:
**SIGINT handler doesn't re-signal the process**

The shutdown closure calls `void stopSsrFProxy(ssrfProxyHandle)` and returns. Because the `process.once("SIGINT")` listener is consumed, the first Ctrl+C only fires the cleanup and leaves the process running until `stopSsrFProxy` resolves or the user presses Ctrl+C again. Whether this matters in practice depends on whether another SIGINT handler elsewhere already handles process exit — worth verifying.

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb984817c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/infra/net/ssrf-proxy/proxy-process.ts Outdated
Comment thread src/infra/net/ssrf-proxy/proxy-lifecycle.ts Outdated
@jesse-merhi
jesse-merhi force-pushed the feat/ssrf-network-proxy branch from cb98481 to 29bc4b3 Compare April 22, 2026 07:34
@aisle-research-bot

aisle-research-bot Bot commented Apr 22, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 4 potential security issue(s) in this PR:

# Severity Title
1 🟠 High DNS rebinding SSRF bypass in strict mode when managed env proxy is active
2 🟡 Medium Sensitive data exposure via incomplete proxy URL redaction in container loopback check error
3 🔵 Low Loopback proxy URL check for container-target can be bypassed via DNS-resolving hostnames
4 🔵 Low Loopback proxy forwarding check bypass via non-canonical IPv4 hostnames
1. 🟠 DNS rebinding SSRF bypass in strict mode when managed env proxy is active
Property Value
Severity High
CWE CWE-367
Location src/infra/net/fetch-guard.ts:361-375

Description

In fetchWithSsrFGuard strict mode, when OPENCLAW_PROXY_ACTIVE=1 and any proxy env var is configured, the code switches to EnvHttpProxyAgent but does not pin DNS for the actual outbound connection.

  • Input: params.url / parsedUrl.hostname is attacker-controlled in typical SSRF threat models.
  • Guard/check: resolvePinnedHostnameWithPolicy(parsedUrl.hostname, { policy }) resolves and validates the current DNS answers against the SSRF policy.
  • Sink: createHttp1EnvHttpProxyAgent() forwards the request to the proxy, where the proxy performs its own DNS resolution later (e.g., at CONNECT time).

Because the pinned DNS result is discarded, an attacker who controls DNS for a hostname can:

  1. Make the hostname resolve to a public IP during the guard’s resolution step (passes policy).
  2. Rebind the hostname to a private/loopback/metadata IP before the proxy opens the upstream connection.
  3. The proxy then resolves to the private IP and connects, bypassing OpenClaw’s strict-mode DNS pinning and private-range protections.

Vulnerable code (new branch):

} else if (canUseManagedProxy) {
  await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
    lookupFn: params.lookupFn,
    policy: params.policy,
  });
  dispatcher = createHttp1EnvHttpProxyAgent(undefined, timeoutMs);
}

This is a regression relative to the non-proxy strict path, which uses createPinnedDispatcher(...) to ensure the connection uses the vetted/pinned lookup.

Recommendation

Preserve strict-mode DNS pinning even when routing via the managed env proxy.

One approach is to create a pinned dispatcher in env-proxy mode so the CONNECT/upstream uses the pinned lookup:

} else if (canUseManagedProxy) {
  const pinned = await resolvePinnedHostnameWithPolicy(parsedUrl.hostname, {
    lookupFn: params.lookupFn,
    policy: params.policy,
  });
  dispatcher = createPinnedDispatcher(
    pinned,
    { mode: "env-proxy" },
    params.policy,
    timeoutMs,
  );
}

Alternatively, if the intent is to delegate SSRF enforcement to the operator-managed proxy, then:

  • Make that trust boundary explicit by using a separate guarded mode (similar to TRUSTED_ENV_PROXY) and documenting that strict-mode SSRF protections are no longer guaranteed.
  • Or enforce that the proxy is local/trusted and that it implements connect-time destination IP filtering.

The safest change is to keep pinning at the application layer as shown above.

2. 🟡 Sensitive data exposure via incomplete proxy URL redaction in container loopback check error
Property Value
Severity Medium
CWE CWE-532
Location src/cli/container-target.ts:172-214

Description

The container targeting logic throws an error when OPENCLAW_PROXY_URL points to a loopback host. The error message includes a "redacted" version of the proxy URL, but the redaction only removes credentials, query string, and fragment — it does not remove the URL pathname.

If an operator encodes secrets in the path (common patterns: http://proxy/token/..., http://proxy/<api-key>, etc.), those secrets will be included in the thrown error message. CLI errors are commonly printed to stderr/logs by callers, so this can leak secrets into terminal output, CI logs, or telemetry.

Vulnerable code:

throw new Error(
  `OPENCLAW_PROXY_URL=${redactProxyUrlForMessage(proxyUrl)} is loopback; ...`
);

function redactProxyUrlForMessage(raw: string): string {
  const url = new URL(raw);
  ...
  url.search = "";
  url.hash = "";
  return url.toString();
}

Notes:

  • Tests cover redacting username/password/query/fragment, but do not cover redacting pathname.
  • URL.toString() will include the full path (and can normalize hosts, e.g. 127.1127.0.0.1), making path leaks likely when present.

Recommendation

Redact the proxy URL more aggressively in user-visible error messages. Prefer logging only the origin (scheme + host + port), or explicitly remove the pathname as well.

For example, redact to origin:

function redactProxyUrlForMessage(raw: string): string {
  try {
    const url = new URL(raw);// Keep host/port for diagnostics, drop everything else.
    return url.origin;
  } catch {
    return "<invalid URL>";
  }
}

If you need to keep a hint that a path existed, replace it with a fixed placeholder:

url.username = url.username ? "redacted" : "";
url.password = url.password ? "redacted" : "";
url.pathname = "/";
url.search = "";
url.hash = "";

Add a regression test with a secret in the path (e.g. http://u:p@​127.0.0.1:3128/secret-token) and assert it does not appear in the thrown message.

3. 🔵 Loopback proxy URL check for container-target can be bypassed via DNS-resolving hostnames
Property Value
Severity Low
CWE CWE-20
Location src/cli/container-target.ts:159-175

Description

maybeRunCliInContainer forwards OPENCLAW_PROXY_URL into the container (as --env OPENCLAW_PROXY_URL=...) and tries to prevent loopback proxy URLs because 127.0.0.1 inside a container points to the container itself.

However, assertContainerProxyUrlIsReachable only blocks literal loopback hostnames/IPs based on URL.hostname and isIP(). Hostnames that resolve to loopback (e.g., 127.0.0.1.nip.io, an internal DNS record, or a rebinding domain) are not detected and will be forwarded into the container.

Impact:

  • The intended safety guarantee (“reject loopback proxy URLs for container-targeted commands”) can be bypassed.
  • This can cause unexpected proxy misrouting / denial of service for container executions (traffic attempts to use a proxy on container loopback).
  • In environments where OPENCLAW_PROXY_URL is attacker-influenced (wrapper scripts/CI/shared shells), this creates a trivial way to break network behavior for container-targeted commands.

Vulnerable code:

if (!isLoopbackProxyHostname(parsed.hostname)) {
  return;
}

isLoopbackProxyHostname does not perform DNS resolution, so non-literal hostnames resolving to 127.0.0.0/8 or ::1 are not caught.

Recommendation

Consider strengthening the validation so it matches the safety goal (“reject proxy URLs that would be loopback from inside the container”):

  1. Resolve the hostname and reject if it maps to loopback (and optionally other non-routable/private ranges), e.g. using dns.promises.lookup(hostname, { all: true }).
  2. Be explicit about trade-offs:
    • DNS lookups can be slow/blocking if done synchronously; prefer async or cache.
    • DNS rebinding can still occur between validation and use; if you add DNS checks, document that they are best-effort.
  3. Alternatively (or additionally), document clearly that only literal loopback forms are rejected, and recommend using a literal IP or container-reachable hostname.

Example (async) approach:

import { lookup } from "node:dns/promises";
import { isIP } from "node:net";

async function assertNotLoopbackHostname(hostname: string) {
  const addrs = await lookup(hostname, { all: true });
  for (const { address } of addrs) {// reject 127.0.0.0/8 and ::1 (and optionally private/link-local)// ...
  }
}

If staying synchronous, consider a conservative policy: disallow non-literal hostnames for container proxy URLs unless explicitly overridden (environment flag) to avoid DNS ambiguity.

4. 🔵 Loopback proxy forwarding check bypass via non-canonical IPv4 hostnames
Property Value
Severity Low
CWE CWE-693
Location src/cli/container-target.ts:178-199

Description

The container proxy forwarding safeguard in assertContainerProxyUrlIsReachable() relies on isLoopbackProxyHostname(parsed.hostname) to detect loopback destinations before forwarding OPENCLAW_PROXY_URL into a container.

However, isLoopbackProxyHostname() only treats loopback as:

  • localhost (with trailing dots trimmed)
  • IPv4 addresses recognized by node:net.isIP() (dotted-quad and whatever forms isIP accepts)
  • a small set of IPv6 loopback / IPv4-mapped IPv6 patterns

This can be bypassed with non-canonical numeric IPv4 representations that URL will preserve in hostname but isIP() will not classify as IPv4, while underlying networking stacks (or proxy libraries) may still interpret them as numeric IPs (commonly resolving to 127.0.0.1 for decimal 2130706433, octal/hex variants, etc.).

As a result, a proxy URL that effectively targets loopback can slip past the intended protection and be forwarded into the container.

Vulnerable code:

if (isIP(normalizedHostname) === 4) {
  return normalizedHostname.split(".", 1)[0] === "127";
}// ...otherwise it is treated as not-loopback
return false;

Impact depends on how the proxy URL is later used, but it undermines the safety check meant to prevent accidentally forwarding host-loopback proxy configuration into containers.

Recommendation

Harden loopback detection against alternative IPv4 textual forms.

Options:

  1. Resolve and classify: perform a DNS resolution step for parsed.hostname (e.g., dns.lookup(host, { all: true, verbatim: true })) and reject if any returned address is loopback.
  2. Block ambiguous numeric-only hostnames: if hostname matches patterns like ^\d+$, ^0x[0-9a-f]+$, or octal forms, treat them as unsafe and reject (or require the explicit override env var).
  3. Reuse an existing IP utility if available (e.g., a shared isLoopbackIpAddress()), but ensure it is applied to resolved addresses, not only net.isIP()-recognized literals.

Example using resolution:

import { lookup } from "node:dns/promises";
import { isLoopbackIpAddress } from "../shared/net/ip.js";

async function hostnameResolvesToLoopback(host: string): Promise<boolean> {
  const results = await lookup(host, { all: true, verbatim: true });
  return results.some(r => isLoopbackIpAddress(r.address));
}

If introducing async logic is undesirable here, conservatively reject numeric-only hostnames unless override is set.


Analyzed PR: #70044 at commit e19ce89

Last updated on: 2026-04-28T05:24:58Z

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 29bc4b387d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/infra/net/ssrf-proxy/proxy-lifecycle.ts Outdated
Comment thread src/infra/net/ssrf-proxy/caddy-config.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d695b1794c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/run-main.ts Outdated
@jesse-merhi
jesse-merhi force-pushed the feat/ssrf-network-proxy branch from d695b17 to 9c26630 Compare April 22, 2026 15:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c266305b9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/infra/net/ssrf-proxy/proxy-process.ts Outdated
Comment thread src/infra/net/undici-global-dispatcher.ts Outdated
jesse-merhi pushed a commit that referenced this pull request Apr 22, 2026
Introduces a Caddy-based forward proxy sidecar that intercepts all outbound
HTTP/HTTPS traffic from the Node process. Dual-stack enforcement:

- Layer A: undici EnvHttpProxyAgent (covers fetch())
- Layer B: global-agent bootstrap (covers http.request/https.request)

Caddy's forwardproxy plugin applies IP-based ACL deny rules for
RFC1918/loopback/link-local/cloud-metadata CIDRs, closing the
DNS-rebinding TOCTOU gap that existed in application-level checks.

Includes: config schema, lifecycle management with crash recovery,
startup hook, build script for custom Caddy binary, e2e and unit tests,
and security documentation.

#70044
@jesse-merhi
jesse-merhi force-pushed the feat/ssrf-network-proxy branch from 9c26630 to f20c839 Compare April 22, 2026 16:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f20c8390ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/run-main.ts Outdated
Comment thread src/infra/net/ssrf-proxy/proxy-lifecycle.ts Outdated
@jesse-merhi
jesse-merhi requested a review from a team as a code owner April 23, 2026 01:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9845db3958

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/postinstall-ssrf-caddy.mjs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3bd89f7c82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/infra/net/ssrf-proxy/proxy-process.ts Outdated
Comment thread scripts/postinstall-ssrf-caddy.mjs Outdated
const isWindows = platform === "win32";
const binDir = join(homeDir, ".openclaw", "bin");
const targetName = isWindows ? "caddy-ssrf.exe" : "caddy-ssrf";
const versionedName = isWindows ? `caddy-ssrf-${version}.exe` : `caddy-ssrf-${version}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Key cached Caddy binaries by platform and arch

The cache filename only includes version (caddy-ssrf-<version>), and later logic treats its existence as a valid cache hit, so sharing one HOME across different CPU targets (for example macOS arm64 and Rosetta x64) reuses an incompatible binary and skips downloading the correct asset for the current platform. Include platform/arch in the versioned cache path (or validate the existing binary against platformKey) before returning cached.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7fc5c8c12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/run-main.ts Outdated
Comment thread scripts/postinstall-ssrf-caddy.mjs Outdated
@openclaw-barnacle openclaw-barnacle Bot added the docker Docker and sandbox tooling label Apr 24, 2026
@jesse-merhi
jesse-merhi force-pushed the feat/ssrf-network-proxy branch from a1f7954 to 4d1f68d Compare April 25, 2026 00:33
@openclaw-barnacle openclaw-barnacle Bot added size: L size: XL and removed scripts Repository scripts docker Docker and sandbox tooling size: XL size: L labels Apr 25, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…law#70044)

* feat: support operator-managed proxy routing

* docs: add network proxy changelog entry

* fix(proxy): restrict gateway bypass to loopback IPs

* fix(cli): harden container proxy URL checks

* docs(proxy): clarify gateway bypass scope

* docs: remove proxy changelog entry

* fix(proxy): clear startup CI guard failures

* fix(proxy): harden gateway proxy policy parsing

* fix(proxy): honor update shorthand proxy policy

* fix(cli): redact proxy URL suffixes

* test(proxy): keep gateway help off proxy startup

* fix(proxy): keep overlapping lifecycle active

* docs: add proxy changelog entry

---------

Co-authored-by: joshavant <[email protected]>
timeleft-- added a commit to MachineWisdomAI/ProdClaw that referenced this pull request May 2, 2026
Replace the wholesale upstream feature-train entries that were mistakenly
merged via 'git checkout --theirs CHANGELOG.md' during the cherry-pick
batch with a single ProdClaw 1.0.1-rc.1 section listing only the 20
fixes actually included in this RC.

Per the ProdClaw release-notes contract (Iris docs runbook §10),
ProdClaw release notes describe only what the RC actually contains.

Note count: 21 cherry-picks were originally batched; the CLI text
command hangs fix (upstream openclaw#74220, commit 43ca739) was dropped via
rebase --onto in the previous commit because its supporting
implementation depends on a feature commit (upstream openclaw#70044) introduced
after the v2026.4.20 baseline. 20 cherry-picks remain.

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.
timeleft-- added a commit to MachineWisdomAI/ProdClaw that referenced this pull request May 4, 2026
* fix(security): stop implicit tool grants from config sections (openclaw#47487) (openclaw#75055)

* fix(gateway): align sessions abort wait semantics (openclaw#74751) thanks @BunsDev

Co-authored-by: Val Alexander <[email protected]>

* fix(cron): preserve model overrides for text payloads (openclaw#73946)

Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>

* fix(exec): preserve turnSourceChannel as messageProvider in approval followup runs (openclaw#74666)

When an exec-approval followup run has no deliverable route and no
gateway-internal channel, buildAgentFollowupArgs was passing channel=undefined
to the spawned agent. This left defaults.messageProvider=undefined in the
followup run, causing tools.elevated.allowFrom.<provider> checks to always
fail with provider=null after the user approved an async elevated command.

Thread turnSourceChannel through buildAgentFollowupArgs and use it as a
fallback when sessionOnlyOriginChannel is absent. Fixes openclaw#74646.

Co-authored-by: Claude Sonnet 4.6 <[email protected]>

* fix(feishu): skip empty-text messages with no media to prevent blank session turns (openclaw#74634) (openclaw#74661)

Feishu delivers empty-text events (e.g. {"text":""}) when users send
blank messages or when a media-only message produces no text content.
Writing a blank user turn to the session file causes downstream LLM
providers such as MiniMax to reject requests with:

  invalid params, messages must not be empty (2013)

Guard at the point after media resolution: if ctx.content.trim() is
empty AND mediaList is empty, log the skip and return without queuing
a reply. This preserves all existing behaviour for text, media, and
mixed messages.

Regression test: dispatch a DM with {"text":""} (no media), assert
mockDispatchReplyFromConfig is not called.

Closes openclaw#74634. Thanks @xdengli.

* fix(security): bound bootstrap handoff scopes (openclaw#72919)

* fix(security): remediate CodeQL alerts (#7c5bf1c675)

Iterative HTML tag stripping to prevent nested-tag bypass in text
sanitization. Timing-safe secret comparison for audit checks.

Cherry-picked from 7c5bf1c onto ga/1.0 (v2026.4.20 baseline).

* fix(device-pair): reject invalid remote setup URLs (#7c51cd2baf)

Validate publicUrl and gateway.remote.url before issuing device pairing
setup codes. Prevents user-facing errors from malformed URLs.

Cherry-picked from 7c51cd2 onto ga/1.0 (v2026.4.20 baseline).

* fix: gate startup context for sandboxed spawned sessions (openclaw#73611)

Skip startup context injection for spawned sessions running in sandboxed
mode without workspace write access, preventing context leak.

Cherry-picked from 4808361 onto ga/1.0 (v2026.4.20 baseline).

* fix(gateway): preserve rpc abort terminal snapshots (#0459206c40)

Ensure terminal snapshots are captured when RPC agent runs are aborted,
so wait-for-completion clients receive the final state.

Cherry-picked from 0459206 onto ga/1.0 (v2026.4.20 baseline).

* fix: environment edge case launcher regression (openclaw#74696)

Use Boolean() for NODE_COMPILE_CACHE check instead of !== undefined,
preventing crashes when the variable is set to an empty string.

Cherry-picked from 9177fab onto ga/1.0 (v2026.4.20 baseline).

* fix(agents): finalize embedded lifecycle backstop (#ebff12e84f)

Add lifecycle backstop to embedded agent runs that notes events and
ensures proper finalization when runs fail to terminate cleanly.

Cherry-picked from ebff12e onto ga/1.0 (v2026.4.20 baseline).

* fix(agents): preserve string user content when merging turns (#9061d1e4c3)

Normalize string-form user content to content-part arrays before turn
merge, preventing silent data loss in session history sanitization.

Cherry-picked from 9061d1e onto ga/1.0 (v2026.4.20 baseline).

* fix: derive dynamic context-window guard thresholds (#13e917e292)

Replace hardcoded context-window guard thresholds with dynamic values
derived from model capabilities, preventing unnecessary truncation.

Cherry-picked from 13e917e onto ga/1.0 (v2026.4.20 baseline).

* fix: reject invalid cron edits on disabled jobs (openclaw#74720)

Validate cron expression before applying edits to disabled jobs,
preventing silent corruption of job state.

Cherry-picked from 3224075 onto ga/1.0 (v2026.4.20 baseline).

* fix(cron): catch croner parse errors in add/update handlers (openclaw#74193)

Wrap croner expression parsing in try-catch to return a structured error
instead of crashing the gateway handler on invalid expressions.

Cherry-picked from d2db67e onto ga/1.0 (v2026.4.20 baseline).

* fix: accept previously documented WhatsApp exposeErrorText key (openclaw#74667)

Add exposeErrorText as a passthrough key in the WhatsApp provider config
schema to prevent validation failures on existing configs.

Cherry-picked from 3c9437a onto ga/1.0 (v2026.4.20 baseline).

* fix: interpolate heartbeat response prefix templates (openclaw#73996)

Wire createReplyPrefixContext into heartbeat runner so template
variables like {model} are interpolated instead of rendered literally.

Cherry-picked from 2d1523e onto ga/1.0 (v2026.4.20 baseline).

* fix(acp): fall through to thread-bound resolution on unresolvable token (openclaw#66299, openclaw#74641)

When an ACP token can't be resolved, fall through to thread-bound
session resolution instead of silently failing the auto-reply.

Cherry-picked from 5716428 onto ga/1.0 (v2026.4.20 baseline).

* fix(mattermost): add WebSocket ping/pong keepalive (openclaw#73979)

Send periodic WebSocket pings to prevent idle connection drops on
Mattermost servers with aggressive timeout policies.

Cherry-picked from 0e97f96 onto ga/1.0 (v2026.4.20 baseline).

* chore(release): bump version to 1.0.1-rc.1

21 cherry-picked fixes from upstream onto ga/1.0 (v2026.4.20 baseline).

* docs(changelog): rewrite v1.0.1-rc.1 entries to RC contents only

Replace the wholesale upstream feature-train entries that were mistakenly
merged via 'git checkout --theirs CHANGELOG.md' during the cherry-pick
batch with a single ProdClaw 1.0.1-rc.1 section listing only the 20
fixes actually included in this RC.

Per the ProdClaw release-notes contract (Iris docs runbook §10),
ProdClaw release notes describe only what the RC actually contains.

Note count: 21 cherry-picks were originally batched; the CLI text
command hangs fix (upstream openclaw#74220, commit 43ca739) was dropped via
rebase --onto in the previous commit because its supporting
implementation depends on a feature commit (upstream openclaw#70044) introduced
after the v2026.4.20 baseline. 20 cherry-picks remain.

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* fix(cron): accept threaded delivery in gateway schema (b6be422)

Backports the upstream gateway-schema fix that allows cron `delivery.threadId`
(string or number) for threaded announce delivery (e.g. Telegram forum
topics). Without this, cron-validation tests cherry-picked alongside the
croner-parse-error fix (openclaw#74193) fail because they exercise threadId support
that the v2026.4.20 baseline schema doesn't accept.

Cherry-picked from b6be422 onto ga/1.0 (v2026.4.20 baseline). Removed
the unused TestDelivery type alias from cron-tool.test.ts since the tests
that referenced it on upstream are not present at baseline.

Pre-flight checks (per Iris docs runbook §6):
- (a) Bug exists at baseline: yes — schema rejects threadId for announce.
- (b) Fix is self-contained: yes — touches only schema/cron.ts and a
  small protocol-helper change in cron-tool.ts.
- (c) Test imports check: yes — adds 1 schema-shape test to cron-tool.test.ts
  and 5 validation tests to cron.validation.test.ts; both compile and
  exercise symbols present at baseline once the schema accepts threadId.

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* test(cron): mock loadConfig in cron.validation.test.ts for baseline

The cherry-picked cron.validation.test.ts (from openclaw#74193 + #b6be422306)
mocks getRuntimeConfig, but the v2026.4.20 baseline cron.ts validation
still reads via loadConfig() directly. Upstream later refactored to
getRuntimeConfig (commit 7f3f108, a broad refactor not appropriate
to backport).

Add loadConfig to the same mock factory so the test fixture is read
regardless of which call path the handler uses. All 8 tests pass.

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* test(cron): add missing loadCronStore import to service.issue-regressions

The cherry-pick of 'fix: reject invalid cron edits on disabled jobs (openclaw#74720)'
added a new test that calls loadCronStore() but the upstream commit didn't
include the import (the import had been added in a previous upstream commit
not in our cherry-pick batch). loadCronStore exists at the v2026.4.20
baseline (src/cron/store.ts:77); just adding the import resolves the
ReferenceError.

All 10 tests in this file pass after the fix.

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* fix(outbound): hold active-delivery claim so reconnect drain skips live sends (c94a870)

MK-51: prevents reconnect drain from re-driving an entry that the live
send is still writing to the adapter. The live delivery path holds an
in-memory active claim for queueId across its send; drain honors that
claim via the same entriesInProgress set used for startup recovery.

Cherry-picked from c94a870 onto ga/1.0 (v2026.4.20 baseline).

Pre-flight checks (per Iris docs runbook §6):
- (a) Bug exists at baseline: yes — without the claim, concurrent
  reconnect drain and live send race over the same entry.
- (b) Fix is self-contained: yes — adds tryClaimActiveDelivery /
  releaseActiveDelivery wrappers around the existing claimRecoveryEntry /
  releaseRecoveryEntry primitives present at v2026.4.20.
- (c) Test imports check: required two adjustments at the baseline:
  1. Add `import { createRecoveryLog } from "./delivery-queue.test-helpers.js"`
     (file exists at baseline, just not previously imported here).
  2. Add a local drainAcct1DirectChatReconnect helper that calls
     drainPendingDeliveries with the directchat key/selector. Upstream
     refactored the WhatsApp-specific helper into a generic
     drainDirectChatReconnectPending after our baseline; the local helper
     mirrors that shape without backporting the rename. All 49 tests pass.

Refs: openclaw#70386, MK-51

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* fix: isolate cron context-engine session keys (openclaw#72292) (a3c51f9)

MK-51: prevents stale cron/system events from polluting unrelated user
turns. Threads runSessionKey through the cron isolated-agent execution
context (prepareCronRunContext / delivery-dispatch / run-executor) so
the run-specific session entry is no longer silently aliased to the
agent-wide session entry. Previous behavior could cause queued cron
delivery context to bleed into the next user message in the main
session.

Cherry-picked from a3c51f9 onto ga/1.0 (v2026.4.20 baseline).

Pre-flight checks (per Iris docs runbook §6):
- (a) Bug exists at baseline: yes — at v2026.4.20 the cron run context
  reuses the agent session key for the per-run state, so cron-emitted
  system events accumulate against the main session and surface on the
  next user turn.
- (b) Fix is self-contained: yes — runSessionKey already exists in
  run-session-state.ts at baseline; this commit threads it through
  prepareCronRunContext / dispatchCronDelivery / createCronPromptExecutor
  so the run-scoped entry is keyed to runSessionKey instead of being
  aliased to agentSessionKey.
- (c) Test imports check: 75/75 tests pass after applying conflict
  resolution per runbook §7 (Pattern A: keep the fix's runSessionKey
  parameter at HEAD's structural call sites; Pattern E: take ours for
  CHANGELOG to rewrite at the end). The unused createMessageToolExecutor
  helper was removed since the upstream tests that exercise it are not
  cherry-picked here.

Refs: openclaw#72292, MK-51

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* fix(cron): preserve current delivery target context (e309fd4)

MK-52: cron announce delivery jobs created from a Telegram (or other
channel) context now persist the current delivery target metadata
(channel/to/accountId/threadId) into the cron tool's job spec, so
unattended runs deliver to the originating chat instead of erroring
with "Delivering to <channel> requires target <chatId>".

Cherry-picked from e309fd4 onto ga/1.0 (v2026.4.20 baseline).

Pre-flight checks (per Iris docs runbook §6):
- (a) Bug exists at baseline: yes — at v2026.4.20 createCronTool does
  not receive a current-delivery-context, so Telegram-originated
  cron announce jobs save without a routable target and silently fail
  on later runs (the WOD/Fajr scheduler incident, MK-52).
- (b) Fix is self-contained: yes — adds an optional
  currentDeliveryContext field to CronToolOptions and threads it
  through createCronTool. Both the field shape and the agentChannel /
  currentChannelId / agentTo / agentAccountId / currentThreadTs /
  agentThreadId properties exist at the v2026.4.20 baseline.
- (c) Test imports check: dropped one upstream test ("passes the
  resolved shared config into the tts tool") that depends on a
  post-baseline TTS config refactor (resolveSharedTtsConfig). The
  MK-52 fix is exercised by the kept "passes preserved channel
  delivery context into the cron tool" test. 53/53 tests pass.

Conflict resolution per runbook §7: Pattern A (HEAD removed the
embedded check around createCanvasTool/nodesTool/createCronTool;
re-applied the fix's currentDeliveryContext into HEAD's flat call
site).

Refs: MK-52

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* test(mattermost): skip unrelated post-baseline routing test

The cherry-picked monitor.inbound-system-event.test.ts contains one test
('does not enqueue regular user posts as system events') that exercises
a post-baseline routing decision in monitor.ts. This test was incidental
context in cherry-pick 0e97f96 (the actual ping/pong keepalive fix
for openclaw#73979 lives in monitor-websocket.ts and is exercised by
monitor-websocket.test.ts).

Skip the failing test with a comment pointing at the baseline gap so
it can be re-enabled when the routing fix lands in a future GA line.

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* test(qr-cli): skip URL-validation test that needs stricter parser

The cherry-picked qr-cli.test.ts contains one test that asserts
"Configured gateway.remote.url is invalid." for input
"http://localhost:notaport". At the v2026.4.20 baseline, normalizeUrl()
in setup-code.ts uses Node's URL parser, which silently accepts
"http://localhost:notaport" as host=localhost with no port (treating
:notaport as path). The stricter port validator that catches this case
is upstream of our baseline and not part of cherry-pick a58c4d8.

The remote-URL rejection path is still exercised end-to-end by
setup-code.test.ts (which uses URLs the baseline parser does reject).

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* test(whatsapp): trim post-baseline systemPrompt tests from cherry-pick

The cherry-picked zod-schema.providers-whatsapp.test.ts contained 4 tests
for `systemPrompt` validation across groups/direct/accounts surfaces.
That field is post-baseline and not part of cherry-pick 3c9437a (which
adds deprecated `exposeErrorText` no-op handling). Trimmed the systemPrompt
tests; kept the 2 exposeErrorText tests that exercise the actual fix.

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* test(pairing): trim setup-code tests that need stricter URL parser

Skip 2 tests + 2 it.each cases from cherry-pick a58c4d8 that exercise
post-baseline URL validation:
- "normalizes bare publicUrl host ports for setup code payloads": needs
  upstream's bare host:port normalizer.
- "rejects invalid gateway.remote.url before falling back": needs
  upstream's stricter port validator.
- it.each: dropped "http://localhost:notaport" and "http:/localhost:notaport"
  (Node URL parser accepts these; baseline normalizeUrl returns ws://localhost
  with no port). The other 6 invalid-URL cases still pass.

The actual fix is preserved (the rejection error messages exist for
parser-rejected URLs). Per Iris docs runbook §6c (test imports check).
FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* test(acp): skip 5 post-baseline ACP feature tests from cherry-pick

Cherry-pick 5716428 (ACP fall-through to thread-bound resolution)
brought 5 tests that exercise post-baseline ACP features not present
at v2026.4.20:
- Telegram topic ACP spawn binding (delivery.pin)
- Matrix --bind here without thread spawn
- Matrix thread-bound spawns from top-level rooms
- Bound-thread /acp close with text commands disabled
- acpx plugins.allow gating

The actual fix (fall-through to thread-bound resolution when token is
unresolvable) is exercised by the other 40 tests in this file, all of
which pass at this baseline.

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* test(device-pair): skip 9 URL-validation tests needing stricter parser

Cherry-pick a58c4d8 (device-pair invalid setup URLs) brought 9 tests
that exercise upstream's stricter URL validator:
- 1 test for "localhost:notaport" bare host:port
- 1 test for "http://localhost:notaport" remote URL
- 7 it.each cases for various URL forms accepted by the baseline
  normalizeUrl() but rejected upstream

The baseline uses Node's URL parser, which silently accepts URLs like
"http://localhost:notaport" (treating :notaport as path) and many of
the it.each cases. The actual fix (rejection error messages plus the
validation hook in setup-code.ts) is preserved; only the parser
strictness gap remains.

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* test(agents): skip 16 post-baseline sanitize-history tests from cherry-pick

Cherry-pick 9061d1e (preserve string user content in turn merge)
brought a 56-test session-history sanitization file. 16 tests exercise
post-baseline behavior not part of the cherry-pick:
- Codex-style aborted tool result synthesis (4 tests)
- openai reasoning paired-vs-orphaned model snapshot tracking (2)
- copied inbound metadata stripping (2)
- Gemma 4 OpenAI-compatible reasoning replay strip (1)
- Anthropic latest-thinking-replay preservation (1)
- it.each: thinking-only assistant turn preservation, invalid thinking
  signature stripping, omitted-reasoning fallback (3 it.each blocks
  × 2 providers = 6 cases)

The actual fix (string user content normalization in turn merge) is
exercised by the other 40 tests in this file, all of which pass at
this baseline.

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* test(auto-reply): skip 21 post-baseline tests in agent-runner-execution

Cherry-pick ebff12e (embedded lifecycle backstop) brought a 57-test
file. 21 tests exercise post-baseline behavior not part of the
backstop fix:
- compaction-buffer hint heartbeat-model evidence threading (3)
- static extra system prompt forwarding to CLI backends
- CLI messageProvider live-session resolution
- model capacity error surfacing (mid-turn + pre-reply, 2)
- GPT-5 result classification (5)
- compaction completion notices (notifyUser-enabled + incomplete, 2)
- sanitized generic errors on external chat channels with verbose
- Discord raw runner failure copy variants (2 it.each + 1 standalone)
- Codex API payload formatting for verbose external errors
- direct provider auth guidance for missing API keys

The actual lifecycle-backstop fix is exercised by the other 36 tests
in this file, all of which pass at this baseline.

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* docs(changelog): add MK-51 / MK-52 fix entries to v1.0.1-rc.1

Update the CHANGELOG section to reflect the comprehensive fix set
(now 24 cherry-picks from 21):

- MK-51: c94a870 (outbound active-delivery claim) +
  a3c51f9 (cron context-engine session keys)
- MK-52: e309fd4 (cron preserve current delivery target context)

These are the upstream fixes that were the original motivation for
needing a newer OpenClaw release (Iris was held on 2026.4.14 per
MK-49; the runtime fixes for MK-51/52 landed upstream after our
v2026.4.20 baseline). The fixes are highlighted in the CHANGELOG
because they map directly to production incidents.

Per Iris docs runbook §11 (release-notes contract). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* fix(gateway): import isAbortError in agent.ts (PR #4 review fix)

Concrete runtime blocker reported by review: src/gateway/server-methods/agent.ts
calls isAbortError(err) at line 319 (introduced by cherry-pick 0459206
"fix(gateway): preserve rpc abort terminal snapshots") but the import was
not threaded through during conflict resolution. The export exists at
src/infra/unhandled-rejections.ts:184 in the v2026.4.20 baseline and is
already used elsewhere in that file at line 352.

Reference: #4 (comment)

FAST_COMMIT used: baseline pre-existing type errors, not introduced here.

* test(gateway): skip 41 post-baseline tests across agent/abort/dedupe

PR #4 reviewer ran the exact changed-test-file command and found 41
failures across 3 files (after the isAbortError import was fixed in
the previous commit). Triaged per runbook §6c:

- src/gateway/server-methods/agent.test.ts (38 skipped)
  - 28 individual tests + 6 it.each cases (channel/replyChannel hint
    × heartbeat/cron/webhook) for post-baseline behavior:
    * trusted/forged group session metadata handling
    * plugin runtime session ownership tagging
    * ACP turn source markers
    * inter-session message timestamping
    * model-run prompt decoration
    * task registry runtime tracking
    * stale session resolution / freshness rules
    * detached task runtime seam dispatch
    * voice wake auto-routing
    * avatar source redaction
    * abort controller registration / chat.abort plumbing
    * pre-dispatch reactivation cleanup
- src/gateway/server-methods/agent-wait-dedupe.test.ts (2 skipped)
  - RPC cancel snapshot preservation under late completion/rejection
- src/gateway/server.chat.gateway-server-chat.test.ts (3 skipped)
  - sessions.abort dashboard runs
  - agent.wait stale dedupe handling

All affected files now pass: agent.test.ts (42 passed | 38 skipped),
agent-wait-dedupe.test.ts (7 passed | 2 skipped),
server.chat.gateway-server-chat.test.ts (16 passed | 3 skipped).

Per Iris docs runbook §6c (test imports check). FAST_COMMIT used:
baseline pre-existing type errors, not introduced here.

* fix(gateway): remove dead refs to upstream task-tracking helpers

Second wave of PR #4 review fixes. After importing isAbortError, running
the full reviewer validation surfaced two more issues:

1. agent.ts had dead ReferenceError-throwing calls to
   tryFinalizeTrackedAgentTask() and resolveFailedTrackedAgentTaskStatus()
   inside if (shouldTrackTask) blocks. Both helpers don't exist at the
   v2026.4.20 baseline (they're upstream wrappers added after our
   baseline; the baseline only exposes createRunningTaskRun /
   completeTaskRunByRunId / failTaskRunByRunId). The cherry-pick
   0459206 brought the calls without the wrapper definitions.

   Removed the dead blocks with comments explaining why. The
   terminal-snapshot benefit (the `aborted` extraction and stopReason
   payload) is preserved. createRunningTaskRun (the only baseline-valid
   call) still fires inside shouldTrackTask.

2. src/agents/pi-tools.policy.test.ts imported
   ./test-helpers/provider-alias-cases.js which doesn't exist at
   baseline. Restored the helper from upstream main (15 lines, pure
   data table — no runtime dependencies).

Per Iris docs runbook §6c. FAST_COMMIT used: baseline pre-existing
type errors, not introduced here.

Refs: #4 (comment)

---------

Co-authored-by: Val Alexander <[email protected]>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: hcl <[email protected]>
Co-authored-by: Claude Sonnet 4.6 <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…law#70044)

* feat: support operator-managed proxy routing

* docs: add network proxy changelog entry

* fix(proxy): restrict gateway bypass to loopback IPs

* fix(cli): harden container proxy URL checks

* docs(proxy): clarify gateway bypass scope

* docs: remove proxy changelog entry

* fix(proxy): clear startup CI guard failures

* fix(proxy): harden gateway proxy policy parsing

* fix(proxy): honor update shorthand proxy policy

* fix(cli): redact proxy URL suffixes

* test(proxy): keep gateway help off proxy startup

* fix(proxy): keep overlapping lifecycle active

* docs: add proxy changelog entry

---------

Co-authored-by: joshavant <[email protected]>
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
…law#70044)

* feat: support operator-managed proxy routing

* docs: add network proxy changelog entry

* fix(proxy): restrict gateway bypass to loopback IPs

* fix(cli): harden container proxy URL checks

* docs(proxy): clarify gateway bypass scope

* docs: remove proxy changelog entry

* fix(proxy): clear startup CI guard failures

* fix(proxy): harden gateway proxy policy parsing

* fix(proxy): honor update shorthand proxy policy

* fix(cli): redact proxy URL suffixes

* test(proxy): keep gateway help off proxy startup

* fix(proxy): keep overlapping lifecycle active

* docs: add proxy changelog entry

---------

Co-authored-by: joshavant <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…law#70044)

* feat: support operator-managed proxy routing

* docs: add network proxy changelog entry

* fix(proxy): restrict gateway bypass to loopback IPs

* fix(cli): harden container proxy URL checks

* docs(proxy): clarify gateway bypass scope

* docs: remove proxy changelog entry

* fix(proxy): clear startup CI guard failures

* fix(proxy): harden gateway proxy policy parsing

* fix(proxy): honor update shorthand proxy policy

* fix(cli): redact proxy URL suffixes

* test(proxy): keep gateway help off proxy startup

* fix(proxy): keep overlapping lifecycle active

* docs: add proxy changelog entry

---------

Co-authored-by: joshavant <[email protected]>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…law#70044)

* feat: support operator-managed proxy routing

* docs: add network proxy changelog entry

* fix(proxy): restrict gateway bypass to loopback IPs

* fix(cli): harden container proxy URL checks

* docs(proxy): clarify gateway bypass scope

* docs: remove proxy changelog entry

* fix(proxy): clear startup CI guard failures

* fix(proxy): harden gateway proxy policy parsing

* fix(proxy): honor update shorthand proxy policy

* fix(cli): redact proxy URL suffixes

* test(proxy): keep gateway help off proxy startup

* fix(proxy): keep overlapping lifecycle active

* docs: add proxy changelog entry

---------

Co-authored-by: joshavant <[email protected]>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…law#70044)

* feat: support operator-managed proxy routing

* docs: add network proxy changelog entry

* fix(proxy): restrict gateway bypass to loopback IPs

* fix(cli): harden container proxy URL checks

* docs(proxy): clarify gateway bypass scope

* docs: remove proxy changelog entry

* fix(proxy): clear startup CI guard failures

* fix(proxy): harden gateway proxy policy parsing

* fix(proxy): honor update shorthand proxy policy

* fix(cli): redact proxy URL suffixes

* test(proxy): keep gateway help off proxy startup

* fix(proxy): keep overlapping lifecycle active

* docs: add proxy changelog entry

---------

Co-authored-by: joshavant <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants