openclaw - ✅(Solved) Fix security audit: downgrade `gateway.trusted_proxies_missing` to `info` when `gateway.bind="loopback"` [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70357Fetched 2026-04-23 07:25:44
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2commented ×1referenced ×1

security audit reports gateway.trusted_proxies_missing as WARN even when gateway.bind="loopback". On a loopback-only gateway, X-Forwarded-For spoofing is impossible because the endpoint is unreachable off-host — the check's own remediation acknowledges this ("…or keep the Control UI local-only.").

The severity should be context-aware: info when bind=loopback, keeping warn when bind is lan / tailnet / custom (where an actual proxy path can exist).

Error Message

security audit reports gateway.trusted_proxies_missing as WARN even when gateway.bind="loopback". On a loopback-only gateway, X-Forwarded-For spoofing is impossible because the endpoint is unreachable off-host — the check's own remediation acknowledges this ("…or keep the Control UI local-only."). The severity should be context-aware: info when bind=loopback, keeping warn when bind is lan / tailnet / custom (where an actual proxy path can exist). "severity": "warn", The detail correctly notes bind is loopback — so the check knows the risk is latent, not active, but still escalates to warn. On single-operator installs (trust model: personal assistant (one trusted operator boundary) per the summary.attack_surface info finding), loopback-only is the default recommended config. Users end up with a permanent warn that is purely informational. Options today:

  • Leave the warn — noise on every audit, erodes signal-vs-noise ratio. severity = "warn" # current behavior for lan/tailnet/custom

Root Cause

On single-operator installs (trust model: personal assistant (one trusted operator boundary) per the summary.attack_surface info finding), loopback-only is the default recommended config. Users end up with a permanent warn that is purely informational. Options today:

  • Silence by setting gateway.trustedProxies=["127.0.0.1"] — semantically wrong (loopback is not a proxy) and pollutes config.
  • Leave the warn — noise on every audit, erodes signal-vs-noise ratio.
  • Local baseline suppressor — works but leaks the problem into every downstream user's baseline file.

Fix Action

Fix / Workaround

Happy to help with a patch if the maintainers point at where severity is assigned in the check.

PR fix notes

PR #70368: fix(security): downgrade trusted_proxies_missing to info on loopback bind

Description (problem / solution / changelog)

Summary

  • Problem: security audit always reports gateway.trusted_proxies_missing as warn, even when gateway.bind="loopback". On a loopback-only bind the port is unreachable off-host, so X-Forwarded-For spoofing is not a real attack path. The check's own detail already called this out, but severity stayed warn.
  • Why it matters: The default single-operator install (loopback, Control UI enabled) sits on a permanent warn in every audit run. Users either silence it with a semantically-wrong trustedProxies=["127.0.0.1"], keep the noise, or leak it into local baseline suppressors. All three erode signal-vs-noise in the audit report.
  • What changed: gateway.trusted_proxies_missing now emits severity: "info" when gateway.bind="loopback" and severity: "warn" when bind is non-loopback (lan/tailnet/custom). The existing branch guarded bind === "loopback"; that guard is removed so non-loopback binds with empty trustedProxies also surface the finding, keeping the runtime safety posture intact.
  • What did NOT change (scope boundary): No other checks are touched. gateway.loopback_no_auth, gateway.bind_no_auth, and the Control UI origin checks keep their current severities and conditions.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Security/audit severity taxonomy.

Linked Issue/PR

  • Closes #70357
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: the literal severity: "warn" inside the bind === "loopback" && controlUiEnabled && trustedProxies.length === 0 branch at src/security/audit-gateway-config.ts:147. The check already established that the risk is latent on loopback but kept the elevated severity.
  • Missing detection / guardrail: no context-aware severity on this particular check, and no matching branch for non-loopback binds missing trustedProxies.
  • Contributing context (if known): the rest of collectGatewayConfigFindings already branches on bind for severity/conditions (e.g., gateway.tools_invoke_http.dangerous_allow uses extraRisk ? "critical" : "warn"), so the pattern is familiar in this file.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/security/audit-loopback-logging.test.ts
  • Scenario the test should lock in: (a) loopback + controlUi enabled + empty trustedProxies yields info; (b) non-loopback (lan) + controlUi enabled + empty trustedProxies + token auth yields warn. The test helper's severity parameter is widened to "info" | "warn" | "critical" to express (a).
  • Why this is the smallest reliable guardrail: collectGatewayConfigFindings is the seam; both branches now run through it and the assertion is on the emitted severity and checkId, not on copy.
  • Existing test that already covers this (if any): the loopback-warn case already existed; flipped to expect info and added the non-loopback-warn mirror case.
  • If no new test is added, why not: N/A (added).

User-visible / Behavior Changes

  • openclaw security audit on a default loopback install: gateway.trusted_proxies_missing moves from warn to info. Existing baseline suppressors referencing this checkId continue to work without change.
  • openclaw security audit with gateway.bind=lan|tailnet|custom and empty trustedProxies: now surfaces gateway.trusted_proxies_missing at warn, which the previous check had silenced on non-loopback binds. The existing gateway.bind_no_auth critical finding on non-loopback without auth is unchanged.

Diagram (if applicable)

N/A

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: N/A. Runtime safety posture is unchanged; only the report severity classification moves. The check now also catches a real gap for non-loopback binds that the old conditional was silencing.

Repro + Verification

Environment

  • OS: macOS 15 (Darwin 25.3)
  • Runtime/container: Node 22, pnpm
  • Model/provider: N/A (audit check)
  • Integration/channel (if any): N/A
  • Relevant config: gateway.bind="loopback", gateway.controlUi.enabled=true, gateway.trustedProxies=[].

Steps

  1. openclaw config get gateway.bind -> loopback
  2. openclaw security audit --json | jq '.findings[] | select(.checkId=="gateway.trusted_proxies_missing")'

Expected

Finding with severity: "info" titled "Trusted proxies not required on loopback".

Actual (before this PR)

Finding with severity: "warn" titled "Reverse proxy headers are not trusted".

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Local checks before push:

  • pnpm test src/security/audit-loopback-logging.test.ts: 1 file, 1 test pass.
  • pnpm check:changed --staged (pre-commit): full unit-fast lane runs 43 files, 416 tests, all pass.
  • pnpm format:check / pnpm lint:core on touched files: clean.

Human Verification (required)

  • Verified scenarios:
    • Loopback + controlUi + empty trustedProxies yields info.
    • lan + controlUi + token auth + empty trustedProxies yields warn.
    • Existing gateway.loopback_no_auth critical finding still fires on loopback + controlUi + no auth.
    • The unit-fast lane (416 tests across 43 files) still passes.
  • Edge cases checked: the lan-case test sets a placeholder auth.token so the bind_no_auth critical branch does not mask the expected trusted_proxies_missing finding; the assertion is on trusted_proxies_missing specifically.
  • What I did not verify: whether tailnet/custom binds emit the new warn (behaviorally equivalent branch but untested explicitly here).

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes (baseline suppressors keyed on checkId still work; severity downgrade means fewer noisy findings).
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Users who surfaced the old warn in dashboards may stop seeing the loopback case.
    • Mitigation: the finding is still emitted as info, same checkId; dashboards filtering by severity can include info if they want visibility on the loopback case.
  • Risk: The new non-loopback warn may surface on existing lan/tailnet/custom deployments that had empty trustedProxies and were previously silent.
    • Mitigation: the new finding is accurate for those deployments (trustedProxies really is required off-loopback); remediation string points at the fix.

Changed files

  • src/config/gateway-bind.test.ts (added, +58/-0)
  • src/config/gateway-bind.ts (added, +45/-0)
  • src/security/audit-gateway-config.ts (modified, +34/-15)
  • src/security/audit-loopback-logging.test.ts (modified, +154/-2)

PR #70377: fix(security): downgrade trusted_proxies_missing to info on loopback bind

Description (problem / solution / changelog)

Summary

Fixes #70357. On `gateway.bind=loopback`, the Control UI endpoint is unreachable off-host, so X-Forwarded-For spoofing requires local-host access. The check's own remediation already acknowledges this ('or keep the Control UI local-only'), but the severity stays at `warn` — leaving single-operator installs that follow the recommended local-only config with a permanent warn.

Fix

Split the check by bind mode:

  • loopback → `info` (latent risk, user already mitigated)
  • non-loopback → `warn` (active risk, off-host exposure)

Both branches keep the same checkId so existing filters/automation still match.

Test

Updated the regression test in `audit-loopback-logging.test.ts` to lock in the new split:

  1. Loopback bind → `info` present, `warn` absent
  2. `bind: lan` → `warn` present (new case)

The other assertions in the same test (loopback_no_auth, logging.redact_off) unchanged.

Closes #70357.

Changed files

  • src/security/audit-gateway-config.ts (modified, +21/-1)
  • src/security/audit-loopback-logging.test.ts (modified, +21/-1)

Code Example

$ openclaw config get gateway.bind
loopback

$ openclaw security audit --json | jq '.findings[] | select(.checkId=="gateway.trusted_proxies_missing")'
{
  "checkId": "gateway.trusted_proxies_missing",
  "severity": "warn",
  "title": "Reverse proxy headers are not trusted",
  "detail": "gateway.bind is loopback and gateway.trustedProxies is empty. If you expose the Control UI through a reverse proxy, configure trusted proxies so local-client checks cannot be spoofed.",
  "remediation": "Set gateway.trustedProxies to your proxy IPs or keep the Control UI local-only."
}

---

if gateway.bind in {"loopback"}:
    severity = "info"
    title = "Trusted proxies not required on loopback"
    detail = "... (reminder to set trustedProxies IF bind changes)"
elif gateway.trustedProxies is empty:
    severity = "warn"    # current behavior for lan/tailnet/custom
RAW_BUFFERClick to expand / collapse

Summary

security audit reports gateway.trusted_proxies_missing as WARN even when gateway.bind="loopback". On a loopback-only gateway, X-Forwarded-For spoofing is impossible because the endpoint is unreachable off-host — the check's own remediation acknowledges this ("…or keep the Control UI local-only.").

The severity should be context-aware: info when bind=loopback, keeping warn when bind is lan / tailnet / custom (where an actual proxy path can exist).

Current behavior

$ openclaw config get gateway.bind
loopback

$ openclaw security audit --json | jq '.findings[] | select(.checkId=="gateway.trusted_proxies_missing")'
{
  "checkId": "gateway.trusted_proxies_missing",
  "severity": "warn",
  "title": "Reverse proxy headers are not trusted",
  "detail": "gateway.bind is loopback and gateway.trustedProxies is empty. If you expose the Control UI through a reverse proxy, configure trusted proxies so local-client checks cannot be spoofed.",
  "remediation": "Set gateway.trustedProxies to your proxy IPs or keep the Control UI local-only."
}

The detail correctly notes bind is loopback — so the check knows the risk is latent, not active, but still escalates to warn.

Why this matters

On single-operator installs (trust model: personal assistant (one trusted operator boundary) per the summary.attack_surface info finding), loopback-only is the default recommended config. Users end up with a permanent warn that is purely informational. Options today:

  • Silence by setting gateway.trustedProxies=["127.0.0.1"] — semantically wrong (loopback is not a proxy) and pollutes config.
  • Leave the warn — noise on every audit, erodes signal-vs-noise ratio.
  • Local baseline suppressor — works but leaks the problem into every downstream user's baseline file.

Proposed behavior

if gateway.bind in {"loopback"}:
    severity = "info"
    title = "Trusted proxies not required on loopback"
    detail = "... (reminder to set trustedProxies IF bind changes)"
elif gateway.trustedProxies is empty:
    severity = "warn"    # current behavior for lan/tailnet/custom

Effectively the same runtime safety posture — this only changes the report severity for a configuration that the check already recognizes as low-risk.

Environment

  • OpenClaw: 2026.4.21 (f788c88)
  • macOS: Darwin 25.4.0
  • gateway.bind: loopback (default)

Related

  • Trust-model context is already captured by the summary.attack_surface info finding.
  • Similar pattern would be helpful for other context-sensitive checks (e.g., web tool denials on small models — see #70318 area for surrounding audit UX discussion; not a duplicate).

Happy to help with a patch if the maintainers point at where severity is assigned in the check.

extent analysis

TL;DR

Update the gateway.trusted_proxies_missing check to set severity to "info" when gateway.bind is "loopback".

Guidance

  • Review the gateway.trusted_proxies_missing check to determine where the severity is assigned and update it to consider the gateway.bind configuration.
  • Implement a conditional statement to set the severity to "info" when gateway.bind is "loopback", as proposed in the pseudocode.
  • Verify that the updated check correctly reports the severity as "info" when gateway.bind is "loopback" and as "warn" when gateway.bind is "lan", "tailnet", or "custom".
  • Consider applying a similar pattern to other context-sensitive checks to improve the overall accuracy and usefulness of the security audit reports.

Example

if gateway.bind == "loopback":
    severity = "info"
    title = "Trusted proxies not required on loopback"
    detail = "... (reminder to set trustedProxies IF bind changes)"
elif gateway.trustedProxies is empty:
    severity = "warn"

Notes

The proposed update only changes the report severity for a configuration that the check already recognizes as low-risk, and does not affect the runtime safety posture.

Recommendation

Apply the proposed workaround by updating the gateway.trusted_proxies_missing check to set the severity to "info" when gateway.bind is "loopback", as this will improve the accuracy and usefulness of the security audit reports.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix security audit: downgrade `gateway.trusted_proxies_missing` to `info` when `gateway.bind="loopback"` [2 pull requests, 1 comments, 2 participants]