summrise-agent 1.2.510 → 1.2.511

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.
@@ -1,238 +0,0 @@
1
- "use strict";
2
- // Pure URL/origin policy for the desktop shell — extracted from main.ts so
3
- // the security predicates carry unit tests (main.ts imports electron and is
4
- // unimportable under plain node). SHIPPED ALONGSIDE main.js: the npm build
5
- // chain copies it, and `summrise update` stages + swaps it with main/preload.
6
- //
7
- // IPC audit #1 (HIGH): startsWith(BASE) was BYPASSABLE — the string
8
- // 'http://127.0.0.1:18080@evil.com/x' passes the prefix test but Chromium
9
- // parses its host as evil.com (userinfo trick). Every origin decision here
10
- // compares the PARSED origin.
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.BASE_ORIGIN = exports.BASE = void 0;
13
- exports.setAgentPort = setAgentPort;
14
- exports.getAgentPort = getAgentPort;
15
- exports.agentBase = agentBase;
16
- exports.parseAgentPort = parseAgentPort;
17
- exports.isBaseOrigin = isBaseOrigin;
18
- exports.frameUrlOk = frameUrlOk;
19
- exports.controlOriginOk = controlOriginOk;
20
- exports.isDesktopSpaUrl = isDesktopSpaUrl;
21
- exports.isPrivateHost = isPrivateHost;
22
- exports.certBypassAllowed = certBypassAllowed;
23
- exports.sanitizeBrowserUrl = sanitizeBrowserUrl;
24
- exports.setDshPort = setDshPort;
25
- exports.getDshPort = getDshPort;
26
- exports.dshBase = dshBase;
27
- exports.isDshUrl = isDshUrl;
28
- exports.BASE = "http://127.0.0.1:18080";
29
- exports.BASE_ORIGIN = new URL(exports.BASE).origin;
30
- // Configurable agent port (custom-port installs): the shell follows the
31
- // agent's config.yaml server.port (main.ts resolves it at boot via
32
- // SUMMRISE_AGENT_PORT env, else the registry install/data dirs). Predicates
33
- // below compare against the CONFIGURED origin — a hardcoded 18080 would
34
- // deafen the IPC bridge and strand the desktop on any custom port.
35
- // Default stays 18080 (canonical); tests reset via setAgentPort(18080).
36
- let agentPort = null;
37
- function setAgentPort(port) {
38
- if (Number.isInteger(port) && port > 0 && port < 65536)
39
- agentPort = port;
40
- }
41
- function getAgentPort() {
42
- return agentPort ?? 18080;
43
- }
44
- function agentBase() {
45
- return `http://127.0.0.1:${getAgentPort()}`;
46
- }
47
- function agentOrigin() {
48
- return new URL(agentBase()).origin;
49
- }
50
- // server.port out of an agent config.yaml (first `port:` inside the
51
- // top-level `server:` section; null when absent/invalid). Pure so the
52
- // shell's file/registry glue stays untestable-thin and this stays pinned.
53
- function parseAgentPort(yamlText) {
54
- let inServer = false;
55
- for (const raw of String(yamlText || "").split(/\r?\n/)) {
56
- const line = raw.trimEnd();
57
- if (/^\S/.test(line))
58
- inServer = /^server\s*:/.test(line);
59
- if (!inServer)
60
- continue;
61
- const m = /^\s*port\s*:\s*"?(\d{1,5})"?\s*(?:#.*)?$/.exec(line);
62
- if (m) {
63
- const n = Number(m[1]);
64
- if (Number.isInteger(n) && n > 0 && n < 65536)
65
- return n;
66
- return null;
67
- }
68
- }
69
- return null;
70
- }
71
- function isBaseOrigin(url) {
72
- try {
73
- return new URL(url).origin === agentOrigin();
74
- }
75
- catch {
76
- return false;
77
- }
78
- }
79
- // IPC audit #2: preload runs in EVERY frame; a frame may invoke the bridge
80
- // only when its URL carries the pinned origin (main.ts feeds this the raw
81
- // event.senderFrame.url).
82
- function frameUrlOk(url) {
83
- return isBaseOrigin(url || "");
84
- }
85
- // THE LOOPBACK CONTROL API'S ORIGIN VETO (port 9444), and the reason it lives
86
- // here: the HTTP twin of IPC audit #1 was never fixed. `main.ts` tested the
87
- // caller's Origin with
88
- //
89
- // /^(https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?|file:\/\/)/i
90
- //
91
- // which has NO `$` ANCHOR — so `http://127.0.0.1.evil.com`, `http://localhost.evil.com`
92
- // and `http://127.0.0.1x` all passed, and the API reflects the caller's Origin,
93
- // so a foreign page could READ /api/browser-session/list (session URLs) and
94
- // /api/shell/icon-status (local paths) and POST /api/browser-session/open and
95
- // /api/shell/start-agent. That is the SAME class as the `startsWith(BASE)` bug
96
- // this module was extracted to kill; `isDesktopSpaUrl`'s own test pins the
97
- // sibling-host lookalike, and the HTTP path kept the old shape because it was
98
- // the one decision that never moved in here.
99
- //
100
- // An ABSENT Origin is allowed deliberately: the API is also driven by `curl`
101
- // and native tooling, which send none, and `main.ts` documents that carve-out.
102
- // This is a nuisance barrier for browser pages, NOT authentication — the IPC
103
- // bridge stays separately gated on the frame's own origin.
104
- function controlOriginOk(origin) {
105
- if (!origin || origin === "null")
106
- return true; // native tooling / the data: wait page
107
- try {
108
- const u = new URL(origin);
109
- if (u.protocol === "file:")
110
- return true;
111
- if (u.protocol !== "http:" && u.protocol !== "https:")
112
- return false;
113
- return u.hostname === "127.0.0.1" || u.hostname === "localhost";
114
- }
115
- catch {
116
- // Unparseable is NOT allowed: a value the policy cannot read must never be
117
- // treated as one it recognises.
118
- return false;
119
- }
120
- }
121
- // Main-window tripwire allow-list (did-navigate backstop): the desktop SPA
122
- // subtree of the base origin. STRING-PREFIX check (startsWith(BASE +
123
- // "/desktop")) was the exact class IPC audit #1 flagged — compare the PARSED
124
- // origin and the parsed pathname instead. data: (the wait page) and
125
- // about:blank are handled by the caller.
126
- function isDesktopSpaUrl(url) {
127
- try {
128
- const u = new URL(url);
129
- if (u.origin !== agentOrigin())
130
- return false;
131
- // Segment semantics: /desktop and /desktop/* — /desktopx is a different
132
- // path, not the SPA mount.
133
- return u.pathname === "/desktop" || u.pathname.startsWith("/desktop/");
134
- }
135
- catch {
136
- return false;
137
- }
138
- }
139
- // Lab-device TLS bypass predicate (device-caught: OpenWrt-style
140
- // self-signed defaults, e.g. the GPON ONT web UI, fail every navigation
141
- // with ERR_CERT_AUTHORITY_INVALID). Private-network hosts MAY bypass cert
142
- // errors; the public internet keeps full validation — a bypassed MITM on
143
- // a lab LAN is contained, on the open web it is not. Pure + unit-tested;
144
- // main.ts feeds it the raw certificate-error URL.
145
- function isPrivateHost(hostname) {
146
- const h = String(hostname || "").trim().toLowerCase();
147
- if (!h)
148
- return false;
149
- if (h === "localhost" || h === "::1" || h === "[::1]")
150
- return true;
151
- if (h.endsWith(".local") || h.endsWith(".local."))
152
- return true;
153
- const parts = h.split(".");
154
- if (parts.length !== 4)
155
- return false;
156
- const nums = [];
157
- for (const p of parts) {
158
- if (!/^\d{1,3}$/.test(p))
159
- return false;
160
- const n = Number(p);
161
- if (n > 255)
162
- return false;
163
- nums.push(n);
164
- }
165
- const [a, b] = nums;
166
- if (a === 10)
167
- return true;
168
- if (a === 172 && b >= 16 && b <= 31)
169
- return true;
170
- if (a === 192 && b === 168)
171
- return true;
172
- if (a === 127)
173
- return true;
174
- if (a === 169 && b === 254)
175
- return true;
176
- return false;
177
- }
178
- // Certificate-error gate for app.on("certificate-error"): true = bypass
179
- // (preventDefault + callback(true)), false = deny. http(s) URLs on
180
- // private hosts only — anything else (public hosts, weird schemes,
181
- // unparsable input) stays fully validated.
182
- function certBypassAllowed(url) {
183
- let u;
184
- try {
185
- u = new URL(String(url || ""));
186
- }
187
- catch {
188
- return false;
189
- }
190
- if (u.protocol !== "http:" && u.protocol !== "https:")
191
- return false;
192
- return isPrivateHost(u.hostname);
193
- }
194
- // AI-opened browser windows must never reach file://, javascript: or
195
- // arbitrary schemes through the CDP-driven session windows.
196
- function sanitizeBrowserUrl(url) {
197
- // stage-n preload audit LOW: coerce to string BEFORE trim() — a renderer
198
- // passing a number/object would throw TypeError in the main process (DoS).
199
- const t = String(url || "about:blank").trim();
200
- if (t === "about:blank")
201
- return t;
202
- try {
203
- const u = new URL(t);
204
- if (u.protocol === "http:" || u.protocol === "https:")
205
- return u.toString();
206
- }
207
- catch { /* fall through */ }
208
- return "about:blank";
209
- }
210
- // THE DSH VIEW'S DOOR. The shell's SECOND embedded view shows the harness's own UI, which
211
- // runs on THIS machine's loopback — so this door is one origin and nothing else, and it is a
212
- // SEPARATE predicate from sanitizeBrowserUrl on purpose: the browser view's door is the open
213
- // internet (an AI drives it), this one is a single port, and a door that is wide for one view
214
- // must not quietly become wide for the other. The DSH view has no address bar and nothing
215
- // drives it but the operator, so there is nothing here to widen it for.
216
- //
217
- // The port is the DSH's own (default 18081 — main.ts resolves SUMMRISE_DSH_PORT first), and
218
- // it is deliberately NOT the agent's port: the agent's origin is where the panel lives, and a
219
- // view that could load the panel could also load /api/* with the panel's own authority.
220
- let dshPort = null;
221
- function setDshPort(port) {
222
- if (Number.isInteger(port) && port > 0 && port < 65536)
223
- dshPort = port;
224
- }
225
- function getDshPort() {
226
- return dshPort ?? 18081;
227
- }
228
- function dshBase() {
229
- return `http://127.0.0.1:${getDshPort()}`;
230
- }
231
- function isDshUrl(url) {
232
- try {
233
- return new URL(url).origin === new URL(dshBase()).origin;
234
- }
235
- catch {
236
- return false;
237
- }
238
- }