Skip to content

Support trusted control UI origins - #295

Open
jamesachurchill wants to merge 2 commits into
ID-Robots:betafrom
jamesachurchill:baxter/issue-232
Open

Support trusted control UI origins#295
jamesachurchill wants to merge 2 commits into
ID-Robots:betafrom
jamesachurchill:baxter/issue-232

Conversation

@jamesachurchill

@jamesachurchill jamesachurchill commented Aug 4, 2026

Copy link
Copy Markdown

Summary

  • add a supported data/control-ui-origins.json allowlist for genuine cross-origin or custom-origin deployments
  • strictly validate exact HTTP(S) origins and merge them with generated gateway defaults
  • apply the same exact scheme, host, and port allowlist to setup-redirect reflection
  • preserve ordinary same-origin local, private-IP, and MagicDNS behavior without requiring configuration

Why this remains necessary

Current OpenClaw already accepts ordinary same-origin private and MagicDNS access, so that broad part of #232 no longer needs a workaround. Genuine cross-origin deployments still require an explicit allowlist, and the pre-start script still replaces generated allowedOrigins on every boot.

This replaces the approach in #255 rather than reviving it unchanged. The prior controlUi.extraAllowedOrigins key is not part of the current strict OpenClaw schema, and #255 did not update the setup proxy's independent redirect-origin boundary.

Security and update behavior

  • exact HTTP(S) origins only; no wildcards, credentials, paths, queries, or fragments
  • malformed entries are ignored with pre-start/proxy warnings and never prevent boot
  • configured origins match scheme, hostname, and non-default port exactly
  • the operator file is under ignored data/, so the updater's hard reset preserves it
  • missing configuration retains current defaults

Verification

  • current beta base: 1,567 tests passed across 128 files
  • focused origin coverage: 68 tests passed
  • targeted ESLint, shell syntax, Python compile, diff, and publication scans passed
  • production Next.js build and TypeScript checks passed
  • current main has the same affected source/dependency surface as beta; the patch was also verified against its current v3.1.11 state during preparation
  • both branches pin OpenClaw 2026.7.1; no OpenClaw runtime change is required because this is a configuration-generation and setup-proxy integration

The repository-wide lint command still reports its existing unrelated beta errors; none are in the changed files.

Closes #232

Summary by CodeRabbit

  • New Features

    • Added configuration for trusted Control UI origins through a JSON file and environment variable.
    • Supports exact origin matching by scheme, host, and port for setup redirects.
    • Preserves configured origin order and removes duplicates.
  • Bug Fixes

    • Invalid origin entries are safely ignored with warnings.
    • Setup redirects now preserve the original host when using trusted origins.
  • Documentation

    • Added configuration and validation guidance to the README.
  • Tests

    • Added comprehensive coverage for origin validation, loading, merging, and redirect behavior.

Add a narrow, strictly-validated escape hatch for genuine cross-origin/
custom-origin Control UI deployments: operator-supplied origins in
data/control-ui-origins.json (or CLAWBOX_CONTROL_UI_ORIGINS_FILE) are
merged into the gateway's generated allowedOrigins and honored by the
Next.js proxy's redirect-origin reflection, with exact scheme+host+port
matching so a configured hostname can't be reflected across other
schemes or ports. Same-origin .local/.ts.net/private access is
unaffected and normally needs no entry.
@jamesachurchill
jamesachurchill requested a review from a team as a code owner August 4, 2026 05:05
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jamesachurchill, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
βš™οΈ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8d4d6f60-8743-4b61-9afc-d6e025d9abe8

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 27305b5 and 722c7af.

πŸ“’ Files selected for processing (8)
  • README.md
  • scripts/gateway-pre-start.sh
  • scripts/gateway_origins.py
  • src/lib/control-ui-origins.ts
  • src/lib/gateway-proxy.ts
  • src/tests/unit/control-ui-origins.test.ts
  • src/tests/unit/gateway-origins.test.ts
  • src/tests/unit/gateway-proxy-origins.test.ts
πŸ“ Walkthrough

Walkthrough

The change adds configurable trusted Control UI origins through a JSON file. Python and TypeScript loaders validate and normalize entries, gateway startup merges them with defaults, and setup redirects use exact configured-origin matching. Documentation and unit tests cover configuration, validation, warnings, deduplication, and fallback behavior.

Changes

Trusted Control UI origins

Layer / File(s) Summary
Origin validation and loading
scripts/gateway_origins.py, src/lib/control-ui-origins.ts, src/tests/unit/control-ui-origins.test.ts, src/tests/unit/gateway-origins.test.ts, README.md
The loaders resolve an environment-selected JSON file, validate HTTP/HTTPS origins, normalize schemes, hosts, ports, and IPv6 values, report invalid entries, remove duplicates, and preserve order. The README documents the configuration format and behavior.
Gateway configuration wiring
scripts/gateway-pre-start.sh, src/tests/unit/gateway-origins.test.ts
The pre-start script loads configured origins, logs warnings, exports valid entries, and merges them into generated allowedOrigins values without duplicates. Tests cover startup wiring, invalid JSON, idempotence, defaults, and helper-module fallback.
Setup redirect origin matching
src/lib/gateway-proxy.ts, src/tests/unit/gateway-proxy-origins.test.ts
Setup redirects retain the original Host header and reflect it only when it matches existing host rules or an exact configured origin. Other hosts use CANONICAL_ORIGIN.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant gateway-proxy
  participant control-ui-origins
  participant OriginsFile
  Browser->>gateway-proxy: Request setup redirect with Host header
  gateway-proxy->>control-ui-origins: Load configured origins
  control-ui-origins->>OriginsFile: Read JSON origin array
  OriginsFile-->>control-ui-origins: Return configured origins
  control-ui-origins-->>gateway-proxy: Return normalized origins and warnings
  gateway-proxy-->>Browser: Redirect to trusted origin or CANONICAL_ORIGIN
Loading

Possibly related PRs

  • ID-Robots/clawbox#98: Updates related trusted-origin and setup-redirect handling in src/lib/gateway-proxy.ts.

Suggested labels: area: ui

Suggested reviewers: yalexx

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… Passed checks (4 passed)
Check name Status Explanation
Title check βœ… Passed The title clearly and concisely describes the primary change: support for trusted Control UI origins.
Description check βœ… Passed The description covers the feature, security behavior, issue linkage, and verification, but omits the template's type and checklist sections.
Linked Issues check βœ… Passed The changes satisfy issue #232 by adding documented origin configuration, strict validation, default merging, warnings, and focused tests.
Out of Scope Changes check βœ… Passed The documentation, scripts, implementation, and tests directly support configurable trusted Control UI origins and the linked issue objectives.
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

πŸ¦€ ClawReview

Scuttled over to say hello and get you oriented πŸ¦€

This PR adds a data/control-ui-origins.json allowlist so operators running the Control UI behind a reverse proxy on a non-standard hostname or port can configure exact trusted origins. Origins are validated strictly (scheme + host + port, no wildcards), loaded on each boot by the gateway pre-start script and at request time by the Next.js proxy, and merged into the gateway's generated allowedOrigins list. Ordinary same-origin access via <hostname>.local, Tailscale .ts.net, or private LAN IPs is unchanged and requires no entry in the file.

At a glance

  • ✨ Feature Β· touches gateway-proxy setup-redirect + gateway pre-start script + new origin-validation library (TypeScript + Python)
  • Base branch: beta Β· +546 source / +902 tests across 8 files
  • βœ… base beta matches the beta-first convention
  • 🟑 title doesn't follow type: description (feat/fix/chore/docs/…)
  • βœ… source changes come with test changes
  • 🟑 large PR (1454 lines changed) β€” consider splitting
  • ℹ️ touches security-sensitive paths (scripts/gateway-pre-start.sh) β€” review with extra care

Good to know

  • 🟑 Modifies scripts/gateway-pre-start.sh, which runs on every boot on customer devices β€” the new Python heredoc block is a meaningful addition to the boot critical path.
  • ℹ️ The origin-validation logic is implemented twice (Python for the pre-start/boot path, TypeScript for the Next.js proxy) and must be kept manually in sync; the PR author notes this explicitly.
  • ℹ️ The config file lives under data/ (gitignored), so it survives the updater's git hard reset β€” consistent with other operator-managed runtime state like config.json.
  • ℹ️ Test suite includes tests that extract and execute actual heredoc snippets from gateway-pre-start.sh directly, catching regressions in the shipped script rather than a re-implementation.

β€” ClawReview πŸ¦€. I set the scene; CodeRabbit reviews the code; you decide. Conventions: docs.

@github-actions github-actions Bot added area: docs Auto-triage area area: install Auto-triage area area: gateway Auto-triage area labels Aug 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/gateway-proxy.ts (1)

67-90: πŸ”’ Security & Privacy | πŸ”΅ Trivial | ⚑ Quick win

Redirect with the normalized origin, not the raw Host header.

isConfiguredOrigin validates a normalized origin, but Line 86 builds the Location URL from the raw hostHeader. The trust decision and the emitted URL come from two different values.

The current code is not exploitable. normalizeOrigin rejects any composed value that carries a path, a query, a fragment, credentials, or *, and the WHATWG parser turns \ and / inside the header into a path, which the path check at src/lib/control-ui-origins.ts Line 82 rejects. So a header that passes the check cannot carry extra structure.

Returning the matched origin still removes the need for that reasoning and emits a canonical Location. It also makes the port and case handling explicit.

πŸ”’οΈ Proposed refactor
-function isConfiguredOrigin(proto: string, hostHeader: string): boolean {
-  const { origin } = normalizeOrigin(`${proto}://${hostHeader}`);
-  return origin !== null && getConfiguredOrigins().has(origin);
-}
+function matchConfiguredOrigin(proto: string, hostHeader: string): string | null {
+  const { origin } = normalizeOrigin(`${proto}://${hostHeader}`);
+  return origin !== null && getConfiguredOrigins().has(origin) ? origin : null;
+}
 
 export function redirectToSetup(request: NextRequest): NextResponse {
   const rawProto = request.headers.get("x-forwarded-proto");
   const proto =
     rawProto
       ?.split(",")
       .map((t) => t.trim().toLowerCase())
       .find((t) => ALLOWED_PROTOS.has(t)) ?? "http";
   const hostHeader = request.headers.get("host");
   const rawHost = hostHeader?.toLowerCase().replace(/:\d+$/, "");
-  const reflectable =
-    !!rawHost &&
-    (isReflectableHost(rawHost) || (!!hostHeader && isConfiguredOrigin(proto, hostHeader)));
-  if (reflectable) {
-    return NextResponse.redirect(
-      new URL(`${proto}://${hostHeader}/setup`),
-      302
-    );
-  }
+  if (rawHost && isReflectableHost(rawHost)) {
+    return NextResponse.redirect(new URL(`${proto}://${hostHeader}/setup`), 302);
+  }
+  const configured = hostHeader ? matchConfiguredOrigin(proto, hostHeader) : null;
+  if (configured) {
+    return NextResponse.redirect(new URL(`${configured}/setup`), 302);
+  }
   return NextResponse.redirect(new URL(`${CANONICAL_ORIGIN}/setup`), 302);
 }

Note: the test at src/tests/unit/gateway-proxy-origins.test.ts Line 56 expects http://custom.example.com:8080/setup, which this refactor still produces.

As per path instructions: "TypeScript server-side libraries. Review for proper error handling and type safety".

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/gateway-proxy.ts` around lines 67 - 90, Update isConfiguredOrigin to
return the normalized matched origin, or otherwise expose that normalized value
to redirectToSetup, instead of returning only a boolean. In redirectToSetup, use
that normalized origin to construct the /setup redirect whenever the origin is
trusted, preserving the existing reflectable-host behavior and configured-origin
port handling.

Source: Path instructions

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 252: Update the CLAWBOX_CONTROL_UI_ORIGINS_FILE row in README.md to
document the absolute default path
/home/clawbox/clawbox/data/control-ui-origins.json, matching the hard-coded
defaults used by the gateway and TypeScript loaders.

In `@scripts/gateway_origins.py`:
- Around line 122-134: The load_configured_origins file-read path must preserve
its never-raises contract for invalid UTF-8; add UnicodeDecodeError handling
after the OSError handler and return no origins with a warning. In
scripts/gateway-pre-start.sh, wrap resolve_origins_path and
load_configured_origins in a broad exception handler that warns to stderr and
exits successfully, allowing startup to continue with no extra origins.
- Around line 60-111: Align the gateway and proxy origin canonicalization
contracts: in scripts/gateway_origins.py, reject raw values containing
backslashes, tabs, CR, LF, percent signs, or non-ASCII characters before
urlsplit, and emit compressed IPv6 using
ipaddress.IPv6Address(hostname).compressed; src/lib/control-ui-origins.ts
requires no direct change. Add matching cases to
src/tests/unit/control-ui-origins.test.ts and
src/tests/unit/gateway-origins.test.ts covering these shared inputs and IPv6
normalization.
- Around line 126-140: Update the file-reading call in the surrounding
origin-loading function to omit the redundant default "r" mode while preserving
encoding. In the non-list JSON error return, parenthesize the adjacent f-strings
so their intentional concatenation is explicit without changing the resulting
message.

In `@scripts/gateway-pre-start.sh`:
- Line 95: Replace the global export of CLAWBOX_GATEWAY_ORIGINS_SCRIPT_DIR with
per-invocation environment scoping on the python3 heredoc call that consumes it,
while preserving the variable’s value for that call and preventing it from
reaching later openclaw or node processes.
- Around line 103-106: Update the import-exception handler in the gateway
pre-start script to write a clear warning to stderr before exiting successfully,
while preserving the non-blocking fallback behavior. Also update the
corresponding gateway-origins test assertion to expect the warning instead of an
empty stderr result.

In `@src/lib/control-ui-origins.ts`:
- Around line 137-152: Update the catch blocks in the control UI origins loading
and JSON parsing flow to handle caught values as unknown without using `as
Error`. Extract the message defensively so thrown non-Error values still produce
meaningful warning text, while preserving the existing warning prefixes and
fallback behavior.

In `@src/lib/gateway-proxy.ts`:
- Around line 56-65: Update getConfiguredOrigins and its cached state to refresh
the configured origins when the source file’s modification time changes, while
retaining the cache when the mtime is unchanged. Track the last observed mtime
alongside cachedConfiguredOrigins and reload through
loadConfiguredOriginsFromEnv when it changes; preserve warning emission and Set
construction for refreshed values.

In `@src/tests/unit/control-ui-origins.test.ts`:
- Around line 88-96: Update the normalizeOrigin tests to assert the warning text
for the out-of-range port and dotted-decimal host cases, confirming they
exercise the invalid-URL branch rather than port or IPv4 validation. Add parity
coverage for the backslash userinfo, embedded-newline hostname, and uncompressed
IPv6 inputs, asserting normalizeOrigin results and matching behavior in the
Python loader tests in gateway-origins.test.ts.
- Around line 189-191: Update the unreadable-path test around
loadConfiguredOrigins to create a temporary origins file, remove its read
permission with chmodSync, and assert the function does not throw. Skip this
test when running as root, and restore permissions and clean up the temporary
file afterward so the readFileSync error handler is actually exercised.

In `@src/tests/unit/gateway-origins.test.ts`:
- Around line 143-193: Update src/tests/unit/gateway-origins.test.ts lines 1,
143-193, and 295-385: import afterEach from vitest, add an afterEach cleanup
hook to both describe blocks, assign dir in the missing-file test, and remove
all inline rmSync calls so temporary directories are cleaned up even when
assertions fail.

In `@src/tests/unit/gateway-proxy-origins.test.ts`:
- Around line 125-154: Make the no-config test hermetic by setting ENV_VAR to a
nonexistent path under dir before importFresh(), rather than deleting it and
allowing the default device path. In the invalid-JSON test, spy on or otherwise
capture console.warn before importFresh(), then assert getConfiguredOrigins
emits a warning while preserving the existing host-reflection assertions.
- Around line 112-123: Extend the gateway proxy origin tests around the existing
β€œdoes not reflect” case to cover a configured IPv4 origin and a request using
the same address with a different port, asserting that the configured port is
not enforced by the current IPv4 matching path. Update the README’s exact-origin
matching claim to explicitly exclude IPv4 hosts, while preserving the existing
documented example and other matching guidance.

---

Outside diff comments:
In `@src/lib/gateway-proxy.ts`:
- Around line 67-90: Update isConfiguredOrigin to return the normalized matched
origin, or otherwise expose that normalized value to redirectToSetup, instead of
returning only a boolean. In redirectToSetup, use that normalized origin to
construct the /setup redirect whenever the origin is trusted, preserving the
existing reflectable-host behavior and configured-origin port handling.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ac52d3bf-3b2a-47dd-ab61-b98af98d9689

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between aeb34cd and 27305b5.

πŸ“’ Files selected for processing (8)
  • README.md
  • scripts/gateway-pre-start.sh
  • scripts/gateway_origins.py
  • src/lib/control-ui-origins.ts
  • src/lib/gateway-proxy.ts
  • src/tests/unit/control-ui-origins.test.ts
  • src/tests/unit/gateway-origins.test.ts
  • src/tests/unit/gateway-proxy-origins.test.ts

Comment thread README.md Outdated
Comment thread scripts/gateway_origins.py
Comment thread scripts/gateway_origins.py
Comment thread scripts/gateway_origins.py Outdated
Comment thread scripts/gateway-pre-start.sh Outdated
Comment thread src/tests/unit/control-ui-origins.test.ts
Comment on lines +189 to +191
it("never throws, even on an unreadable path", () => {
expect(() => loadConfiguredOrigins("/root/definitely-not-readable/origins.json")).not.toThrow();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

This test does not reach the unreadable-file branch.

/root/definitely-not-readable/origins.json does not exist, so fs.existsSync at Line 130 returns false and the function returns early. The test duplicates the missing-file case at Line 144 and never exercises the readFileSync handler at Lines 137-142.

Create a real file, remove its read permission, and skip the test when the process runs as root.

πŸ’š Proposed test that reaches the branch
-    it("never throws, even on an unreadable path", () => {
-      expect(() => loadConfiguredOrigins("/root/definitely-not-readable/origins.json")).not.toThrow();
-    });
+    it.skipIf(typeof process.getuid === "function" && process.getuid() === 0)(
+      "returns a warning for an existing but unreadable file",
+      () => {
+        dir = mkdtempSync(path.join(tmpdir(), "control-ui-origins-"));
+        const file = path.join(dir, "origins.json");
+        writeFileSync(file, JSON.stringify(["http://a.example.com"]));
+        chmodSync(file, 0o000);
+        const result = loadConfiguredOrigins(file);
+        expect(result.origins).toEqual([]);
+        expect(result.warnings).toHaveLength(1);
+      },
+    );

Import chmodSync from node:fs.

πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("never throws, even on an unreadable path", () => {
expect(() => loadConfiguredOrigins("/root/definitely-not-readable/origins.json")).not.toThrow();
});
it.skipIf(typeof process.getuid === "function" && process.getuid() === 0)(
"returns a warning for an existing but unreadable file",
() => {
dir = mkdtempSync(path.join(tmpdir(), "control-ui-origins-"));
const file = path.join(dir, "origins.json");
writeFileSync(file, JSON.stringify(["http://a.example.com"]));
chmodSync(file, 0o000);
const result = loadConfiguredOrigins(file);
expect(result.origins).toEqual([]);
expect(result.warnings).toHaveLength(1);
},
);
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/unit/control-ui-origins.test.ts` around lines 189 - 191, Update the
unreadable-path test around loadConfiguredOrigins to create a temporary origins
file, remove its read permission with chmodSync, and assert the function does
not throw. Skip this test when running as root, and restore permissions and
clean up the temporary file afterward so the readFileSync error handler is
actually exercised.

Comment thread src/tests/unit/gateway-origins.test.ts
Comment thread src/tests/unit/gateway-proxy-origins.test.ts
Comment on lines +125 to +154
it("retains existing ALLOWED_HOSTS/IP reflection when no origins are configured", async () => {
delete process.env[ENV_VAR];
await importFresh();

const request = createRequest("http://clawbox.local/", { host: "clawbox.local" });
const response = gatewayProxy.redirectToSetup(request);

expect(response.headers.get("location")).toContain("clawbox.local");

const ipRequest = createRequest("http://192.0.2.3/", { host: "192.0.2.3" });
const ipResponse = gatewayProxy.redirectToSetup(ipRequest);
expect(ipResponse.headers.get("location")).toContain("192.0.2.3");
});

it("an invalid origins file yields no extras and does not affect existing host reflection", async () => {
const file = path.join(dir, "origins.json");
writeFileSync(file, "{not json");
process.env[ENV_VAR] = file;
await importFresh();

const request = createRequest("http://clawbox.local/", { host: "clawbox.local" });
const response = gatewayProxy.redirectToSetup(request);
expect(response.headers.get("location")).toContain("clawbox.local");

const untrustedRequest = createRequest("http://untrusted.example.com/", {
host: "untrusted.example.com",
});
const untrustedResponse = gatewayProxy.redirectToSetup(untrustedRequest);
expect(untrustedResponse.headers.get("location")).not.toContain("untrusted.example.com");
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Make the no-config test hermetic, and assert the warning.

Two improvements apply:

  • Line 126 deletes the env var, so resolveOriginsPath() returns the absolute default /home/clawbox/clawbox/data/control-ui-origins.json. On a real device that file exists and can hold operator origins, so the test result depends on the machine. Point the env var at a non-existent path inside dir instead.
  • The invalid-JSON test at Lines 139-154 does not check that the warning reaches the log. getConfiguredOrigins calls console.warn at src/lib/gateway-proxy.ts Line 61. The PR objective requires clear reporting of invalid entries, so assert it.
πŸ’š Proposed changes
   it("retains existing ALLOWED_HOSTS/IP reflection when no origins are configured", async () => {
-    delete process.env[ENV_VAR];
+    process.env[ENV_VAR] = path.join(dir, "absent.json");
     await importFresh();
   it("an invalid origins file yields no extras and does not affect existing host reflection", async () => {
     const file = path.join(dir, "origins.json");
     writeFileSync(file, "{not json");
     process.env[ENV_VAR] = file;
     await importFresh();
+    const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
 
     const request = createRequest("http://clawbox.local/", { host: "clawbox.local" });
     const response = gatewayProxy.redirectToSetup(request);
     expect(response.headers.get("location")).toContain("clawbox.local");
+    expect(warn).toHaveBeenCalledWith(expect.stringContaining("not valid JSON"));
+    warn.mockRestore();
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/unit/gateway-proxy-origins.test.ts` around lines 125 - 154, Make
the no-config test hermetic by setting ENV_VAR to a nonexistent path under dir
before importFresh(), rather than deleting it and allowing the default device
path. In the invalid-JSON test, spy on or otherwise capture console.warn before
importFresh(), then assert getConfiguredOrigins emits a warning while preserving
the existing host-reflection assertions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: docs Auto-triage area area: gateway Auto-triage area area: install Auto-triage area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant