@agentsean/adapter-cloudflare 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rewrite.d.ts +18 -7
- package/dist/rewrite.d.ts.map +1 -1
- package/dist/rewrite.js +58 -10
- package/dist/rewrite.js.map +1 -1
- package/package.json +2 -2
package/dist/rewrite.d.ts
CHANGED
|
@@ -3,14 +3,25 @@ export type Overlay = {
|
|
|
3
3
|
metaDescription?: string | undefined;
|
|
4
4
|
};
|
|
5
5
|
export type OverlayMap = Record<string, Overlay>;
|
|
6
|
-
/**
|
|
7
|
-
* Identical HTML for every visitor. The worker must never read User-Agent,
|
|
8
|
-
* bot signals, or crawler class. That is cloaking under Google's spam policies
|
|
9
|
-
* (page last updated 2026-08-28).
|
|
10
|
-
*/
|
|
11
6
|
export declare function rewriteHtml(html: string, overlay: Overlay | undefined): string;
|
|
12
7
|
export declare function overlayFor(url: string, map: OverlayMap): Overlay | undefined;
|
|
13
8
|
export declare function assertWorkerIsNotCloaking(source: string): void;
|
|
14
|
-
/**
|
|
15
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Worker module text shipped to Cloudflare. No UA inspection.
|
|
11
|
+
*
|
|
12
|
+
* This runs on every request to the customer's site, outside the daemon and
|
|
13
|
+
* outside the action validator — so anything it does has to be safe on its own
|
|
14
|
+
* terms. Two properties it must keep:
|
|
15
|
+
*
|
|
16
|
+
* 1. It escapes what it injects. It previously did
|
|
17
|
+
* `"<title>" + overlay.title + "</title>"`, so a title containing
|
|
18
|
+
* `</title><script>` would have injected script into every page served
|
|
19
|
+
* through the overlay. The overlay value is operator-set, not
|
|
20
|
+
* crawler-derived, but a rewriter that trusts its input is one bad KV write
|
|
21
|
+
* away from persistent XSS on the customer's site.
|
|
22
|
+
* 2. The title regex is linear — negated-close inner plus a `|$` fallback —
|
|
23
|
+
* matching the hardening in `@agentsean/actions`. A stall here is a stall
|
|
24
|
+
* in front of the customer's whole site.
|
|
25
|
+
*/
|
|
26
|
+
export declare const WORKER_SOURCE = "\nconst TITLE_EL = /<title\\b[^>]*>(?:[^<]|<(?!\\/title[\\s>]))*(?:<\\/title[^>]*>|$)/i;\n\nfunction escapeHtml(value) {\n return String(value)\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n}\n\nexport default {\n async fetch(request, env) {\n const url = new URL(request.url);\n const originResp = await fetch(request);\n const contentType = originResp.headers.get(\"content-type\") || \"\";\n if (!contentType.includes(\"text/html\")) return originResp;\n const html = await originResp.text();\n const key = url.origin + (url.pathname.replace(/\\/+$/, \"\") || \"/\");\n const raw = await env.SEAN_OVERLAY.get(key);\n if (!raw) {\n return new Response(html, originResp);\n }\n const overlay = JSON.parse(raw);\n let out = html;\n if (overlay.title && TITLE_EL.test(out)) {\n out = out.replace(TITLE_EL, \"<title>\" + escapeHtml(overlay.title) + \"</title>\");\n }\n const headers = new Headers(originResp.headers);\n return new Response(out, { status: originResp.status, headers });\n }\n};\n";
|
|
16
27
|
//# sourceMappingURL=rewrite.d.ts.map
|
package/dist/rewrite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rewrite.d.ts","sourceRoot":"","sources":["../src/rewrite.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"rewrite.d.ts","sourceRoot":"","sources":["../src/rewrite.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA0BjD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAiB9E;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,CAI5E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiB9D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,inCAgCzB,CAAC"}
|
package/dist/rewrite.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
import { patchHtmlTitle } from "@agentsean/actions";
|
|
1
|
+
import { findMetaByName, findOpenTag, patchHtmlTitle } from "@agentsean/actions";
|
|
2
2
|
/**
|
|
3
3
|
* Identical HTML for every visitor. The worker must never read User-Agent,
|
|
4
4
|
* bot signals, or crawler class. That is cloaking under Google's spam policies
|
|
5
5
|
* (page last updated 2026-08-28).
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Escape a value being written into a double-quoted HTML attribute.
|
|
9
|
+
*
|
|
10
|
+
* `patchHtmlTitle` has always escaped what it injects; this file did not, and
|
|
11
|
+
* interpolated `overlay.metaDescription` straight into `content="..."`. A
|
|
12
|
+
* description containing a double quote closes the attribute early and
|
|
13
|
+
* everything after it becomes markup — on every page served through the edge
|
|
14
|
+
* overlay. The overlay is operator-set rather than crawler-derived, so this is
|
|
15
|
+
* defence in depth rather than a live exploit, but the writer is the right
|
|
16
|
+
* place to guarantee it.
|
|
17
|
+
*/
|
|
18
|
+
function escapeAttribute(value) {
|
|
19
|
+
return value
|
|
20
|
+
.replace(/&/g, "&")
|
|
21
|
+
.replace(/</g, "<")
|
|
22
|
+
.replace(/>/g, ">")
|
|
23
|
+
.replace(/"/g, """);
|
|
24
|
+
}
|
|
7
25
|
export function rewriteHtml(html, overlay) {
|
|
8
26
|
if (!overlay)
|
|
9
27
|
return html;
|
|
@@ -11,11 +29,17 @@ export function rewriteHtml(html, overlay) {
|
|
|
11
29
|
if (overlay.title)
|
|
12
30
|
out = patchHtmlTitle(out, overlay.title);
|
|
13
31
|
if (overlay.metaDescription) {
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
const tag = `<meta name="description" content="${escapeAttribute(overlay.metaDescription)}">`;
|
|
33
|
+
// Forward scans rather than regexes — the regex forms were O(n^2) on
|
|
34
|
+
// hostile input (18.5s and 9.9s measured). See @agentsean/actions/tagscan.
|
|
35
|
+
const existing = findMetaByName(out, "description");
|
|
36
|
+
if (existing) {
|
|
37
|
+
out = out.slice(0, existing.start) + tag + out.slice(existing.end);
|
|
16
38
|
}
|
|
17
|
-
else
|
|
18
|
-
|
|
39
|
+
else {
|
|
40
|
+
const head = findOpenTag(out, "head");
|
|
41
|
+
if (head)
|
|
42
|
+
out = out.slice(0, head.end) + tag + out.slice(head.end);
|
|
19
43
|
}
|
|
20
44
|
}
|
|
21
45
|
return out;
|
|
@@ -41,8 +65,34 @@ export function assertWorkerIsNotCloaking(source) {
|
|
|
41
65
|
}
|
|
42
66
|
}
|
|
43
67
|
}
|
|
44
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Worker module text shipped to Cloudflare. No UA inspection.
|
|
70
|
+
*
|
|
71
|
+
* This runs on every request to the customer's site, outside the daemon and
|
|
72
|
+
* outside the action validator — so anything it does has to be safe on its own
|
|
73
|
+
* terms. Two properties it must keep:
|
|
74
|
+
*
|
|
75
|
+
* 1. It escapes what it injects. It previously did
|
|
76
|
+
* `"<title>" + overlay.title + "</title>"`, so a title containing
|
|
77
|
+
* `</title><script>` would have injected script into every page served
|
|
78
|
+
* through the overlay. The overlay value is operator-set, not
|
|
79
|
+
* crawler-derived, but a rewriter that trusts its input is one bad KV write
|
|
80
|
+
* away from persistent XSS on the customer's site.
|
|
81
|
+
* 2. The title regex is linear — negated-close inner plus a `|$` fallback —
|
|
82
|
+
* matching the hardening in `@agentsean/actions`. A stall here is a stall
|
|
83
|
+
* in front of the customer's whole site.
|
|
84
|
+
*/
|
|
45
85
|
export const WORKER_SOURCE = `
|
|
86
|
+
const TITLE_EL = /<title\\b[^>]*>(?:[^<]|<(?!\\/title[\\s>]))*(?:<\\/title[^>]*>|$)/i;
|
|
87
|
+
|
|
88
|
+
function escapeHtml(value) {
|
|
89
|
+
return String(value)
|
|
90
|
+
.replace(/&/g, "&")
|
|
91
|
+
.replace(/</g, "<")
|
|
92
|
+
.replace(/>/g, ">")
|
|
93
|
+
.replace(/"/g, """);
|
|
94
|
+
}
|
|
95
|
+
|
|
46
96
|
export default {
|
|
47
97
|
async fetch(request, env) {
|
|
48
98
|
const url = new URL(request.url);
|
|
@@ -57,10 +107,8 @@ export default {
|
|
|
57
107
|
}
|
|
58
108
|
const overlay = JSON.parse(raw);
|
|
59
109
|
let out = html;
|
|
60
|
-
if (overlay.title) {
|
|
61
|
-
|
|
62
|
-
out = out.replace(/<title[^>]*>[\\s\\S]*?<\\/title>/i, "<title>" + overlay.title + "</title>");
|
|
63
|
-
}
|
|
110
|
+
if (overlay.title && TITLE_EL.test(out)) {
|
|
111
|
+
out = out.replace(TITLE_EL, "<title>" + escapeHtml(overlay.title) + "</title>");
|
|
64
112
|
}
|
|
65
113
|
const headers = new Headers(originResp.headers);
|
|
66
114
|
return new Response(out, { status: originResp.status, headers });
|
package/dist/rewrite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rewrite.js","sourceRoot":"","sources":["../src/rewrite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"rewrite.js","sourceRoot":"","sources":["../src/rewrite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AASjF;;;;GAIG;AACH;;;;;;;;;;GAUG;AACH,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAA4B;IACpE,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,IAAI,OAAO,CAAC,KAAK;QAAE,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,qCAAqC,eAAe,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC;QAC9F,qEAAqE;QACrE,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACpD,IAAI,QAAQ,EAAE,CAAC;YACb,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACrE,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACtC,IAAI,IAAI;gBAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,GAAe;IACrD,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC;IAClE,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,MAAc;IACtD,MAAM,MAAM,GAAG;QACb,aAAa;QACb,WAAW;QACX,YAAY;QACZ,OAAO;QACP,YAAY;QACZ,YAAY;QACZ,eAAe;KAChB,CAAC;IACF,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;QACxB,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,8BAA8B,EAAE,yEAAyE,CAC1G,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgC5B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsean/adapter-cloudflare",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"node": ">=22.19.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@agentsean/actions": "1.0.
|
|
19
|
+
"@agentsean/actions": "1.0.6"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"typescript": "~5.9.3",
|