Skip to content

Commit 588fbb6

Browse files
committed
Allow xterm runtime styles under CSP
1 parent 143edb1 commit 588fbb6

7 files changed

Lines changed: 43 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ The same release also contains fully self-contained installers for direct or off
1818

1919
| Machine | Release asset |
2020
| --- | --- |
21-
| Linux x86-64 | `WebSpider_Install_0.4.6_linux_x64.run` |
22-
| Linux ARM64 | `WebSpider_Install_0.4.6_linux_arm64.run` |
23-
| macOS Intel | `WebSpider_Install_0.4.6_macos_x64.run` |
24-
| macOS Apple silicon | `WebSpider_Install_0.4.6_macos_arm64.run` |
21+
| Linux x86-64 | `WebSpider_Install_0.4.7_linux_x64.run` |
22+
| Linux ARM64 | `WebSpider_Install_0.4.7_linux_arm64.run` |
23+
| macOS Intel | `WebSpider_Install_0.4.7_macos_x64.run` |
24+
| macOS Apple silicon | `WebSpider_Install_0.4.7_macos_arm64.run` |
2525

2626
Run the downloaded asset directly through the system shell:
2727

2828
```bash
29-
sh ~/Downloads/WebSpider_Install_0.4.6_linux_x64.run --workspace /path/to/project
29+
sh ~/Downloads/WebSpider_Install_0.4.7_linux_x64.run --workspace /path/to/project
3030
```
3131

3232
No separate runtime or package-manager setup is part of either user workflow.
@@ -75,7 +75,7 @@ The repository owns the build recipe; GitHub Releases own the binaries. `.github
7575
- `darwin-x64` on `macos-15-intel`;
7676
- `darwin-arm64` on `macos-15`.
7777

78-
Pull requests, `main` pushes, and manual runs produce short-lived Actions artifacts after a clean-install smoke test. A semantic version tag such as `v0.4.6` additionally:
78+
Pull requests, `main` pushes, and manual runs produce short-lived Actions artifacts after a clean-install smoke test. A semantic version tag such as `v0.4.7` additionally:
7979

8080
1. requires the tag to equal the version in `package.json`;
8181
2. gathers exactly four native installers;
@@ -86,8 +86,8 @@ Pull requests, `main` pushes, and manual runs produce short-lived Actions artifa
8686
Create a release by pushing its annotated tag:
8787

8888
```bash
89-
git tag -a v0.4.6 -m "WebSpider 0.4.6"
90-
git push origin v0.4.6
89+
git tag -a v0.4.7 -m "WebSpider 0.4.7"
90+
git push origin v0.4.7
9191
```
9292

9393
For a native development build on the current machine:

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "webspider-fabric",
3-
"version": "0.4.6",
3+
"version": "0.4.7",
44
"private": true,
55
"description": "Distributed, persistent LLM-agent orchestration control plane",
66
"type": "module",

‎src/cli.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ function BunLikeSpawn(executable) {
329329
}
330330

331331
function help() {
332-
process.stdout.write(`WebSpider 0.4.6\n\n`);
332+
process.stdout.write(`WebSpider 0.4.7\n\n`);
333333
process.stdout.write(`Usage:\n`);
334334
process.stdout.write(` webspider up [--listen 127.0.0.1:7340] [--workspace PATH] [--agent-command PATH] [--agent-args JSON]\n`);
335335
process.stdout.write(` webspider hub [--listen 127.0.0.1:7340]\n`);

‎src/hub/hub.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export class Hub {
187187
#registerRoutes() {
188188
const route = (method, pattern, handler, options) => this.router.add(method, pattern, handler, options);
189189

190-
route('GET', '/healthz', async () => ({ status: 'ok', version: '0.4.6', time: nowISO() }), { auth: false, csrf: false });
190+
route('GET', '/healthz', async () => ({ status: 'ok', version: '0.4.7', time: nowISO() }), { auth: false, csrf: false });
191191
route('POST', '/api/v1/auth/login', async (ctx) => {
192192
const body = await readJSON(ctx.request, 16_384);
193193
invariant(typeof body.token === 'string' && secureEqual(body.token, this.ownerToken), 'WS_AUTH_REQUIRED', 'Owner token is invalid.', 401);
@@ -687,7 +687,7 @@ export class Hub {
687687
response.setHeader('referrer-policy', 'no-referrer');
688688
response.setHeader('permissions-policy', 'camera=(), microphone=(), geolocation=()');
689689
response.setHeader('cross-origin-resource-policy', 'same-origin');
690-
response.setHeader('content-security-policy', "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'");
690+
response.setHeader('content-security-policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'");
691691
}
692692

693693
#serveStatic(pathname, response) {

‎src/transport/websocket.js‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ export class WebSocketConnection extends EventEmitter {
3737
this.emit('close');
3838
}
3939
});
40-
this.socket.on('error', (error) => this.emit('error', error));
40+
this.socket.on('error', (error) => {
41+
if (this.listenerCount('error') > 0) this.emit('error', error);
42+
if (!this.closed) {
43+
this.closed = true;
44+
this.emit('close');
45+
}
46+
});
4147
if (head.length) this.#consume(head);
4248
}
4349

‎test/integration.test.js‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,11 @@ test('hub and outbound node provide a root-confined end-to-end API', async (t) =
210210

211211
const portal = await fetch(listening.url);
212212
assert.equal(portal.status, 200);
213-
assert.match(portal.headers.get('content-security-policy'), /object-src 'none'/);
213+
const contentSecurityPolicy = portal.headers.get('content-security-policy');
214+
assert.match(contentSecurityPolicy, /object-src 'none'/);
215+
assert.match(contentSecurityPolicy, /style-src 'self' 'unsafe-inline'/);
216+
assert.match(contentSecurityPolicy, /script-src 'self';/);
217+
assert.doesNotMatch(contentSecurityPolicy, /script-src[^;]*'unsafe-inline'/);
214218
assert.match(await portal.text(), /WebSpider/);
215219
const markdownModule = await fetch(`${listening.url}/markdown.js`);
216220
assert.equal(markdownModule.status, 200);

‎test/websocket.test.js‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { EventEmitter } from 'node:events';
4+
import { WebSocketConnection } from '../src/transport/websocket.js';
5+
6+
class FakeSocket extends EventEmitter {
7+
setNoDelay() {}
8+
write() { return true; }
9+
end() {}
10+
}
11+
12+
test('an abruptly disconnected browser cannot crash the hub', () => {
13+
const socket = new FakeSocket();
14+
const connection = new WebSocketConnection(socket);
15+
let closed = false;
16+
connection.on('close', () => { closed = true; });
17+
assert.doesNotThrow(() => socket.emit('error', Object.assign(new Error('reset'), { code: 'ECONNRESET' })));
18+
assert.equal(closed, true);
19+
});

0 commit comments

Comments
 (0)