diffs: polish viewerBaseUrl validation

This commit is contained in:
Gustavo Madeira Santana 2026-04-02 00:19:19 -04:00
parent 071cbbb31e
commit b140f2e894
5 changed files with 50 additions and 6 deletions

View File

@ -237,6 +237,23 @@ Persistent viewer URL config:
- Plugin-owned fallback for returned viewer links when a tool call does not pass `baseUrl`.
- Must be `http` or `https`, no query/hash.
Example:
```json5
{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
viewerBaseUrl: "https://gateway.example.com/openclaw",
},
},
},
},
}
```
## Security config
- `security.allowRemoteViewer` (`boolean`, default `false`)

View File

@ -112,6 +112,23 @@ Security options:
- `security.allowRemoteViewer` (default `false`): allows non-loopback access to `/plugins/diffs/view/...` token URLs
- `viewerBaseUrl` (optional): persistent viewer-link origin/path fallback for shareable URLs
Example:
```json5
{
plugins: {
entries: {
diffs: {
enabled: true,
config: {
viewerBaseUrl: "https://gateway.example.com/openclaw",
},
},
},
},
}
```
## Example Agent Prompts
Open in canvas:

View File

@ -305,7 +305,7 @@ describe("diffs plugin schema surfaces", () => {
issues: [
{
path: ["viewerBaseUrl"],
message: "baseUrl must use http or https: javascript:alert(1)",
message: "viewerBaseUrl must use http or https: javascript:alert(1)",
},
],
},
@ -382,6 +382,12 @@ describe("diffs viewer URL helpers", () => {
"baseUrl must not include query/hash",
);
});
it("uses the configured field name in viewerBaseUrl validation errors", () => {
expect(() => normalizeViewerBaseUrl("https://example.com?a=1", "viewerBaseUrl")).toThrow(
"viewerBaseUrl must not include query/hash",
);
});
});
describe("renderDiffDocument", () => {

View File

@ -100,7 +100,7 @@ const DiffsPluginJsonSchemaSource = z.strictObject({
.string()
.superRefine((value, ctx) => {
try {
normalizeViewerBaseUrl(value);
normalizeViewerBaseUrl(value, "viewerBaseUrl");
} catch (error) {
ctx.addIssue({
code: "custom",

View File

@ -1,6 +1,7 @@
import type { OpenClawConfig } from "../api.js";
const DEFAULT_GATEWAY_PORT = 18789;
type ViewerBaseUrlFieldName = "baseUrl" | "viewerBaseUrl";
export function buildViewerUrl(params: {
config: OpenClawConfig;
@ -20,18 +21,21 @@ export function buildViewerUrl(params: {
return parsedBase.toString();
}
export function normalizeViewerBaseUrl(raw: string): string {
export function normalizeViewerBaseUrl(
raw: string,
fieldName: ViewerBaseUrlFieldName = "baseUrl",
): string {
let parsed: URL;
try {
parsed = new URL(raw);
} catch {
throw new Error(`Invalid baseUrl: ${raw}`);
throw new Error(`Invalid ${fieldName}: ${raw}`);
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
throw new Error(`baseUrl must use http or https: ${raw}`);
throw new Error(`${fieldName} must use http or https: ${raw}`);
}
if (parsed.search || parsed.hash) {
throw new Error(`baseUrl must not include query/hash: ${raw}`);
throw new Error(`${fieldName} must not include query/hash: ${raw}`);
}
parsed.search = "";
parsed.hash = "";