sync: update from private repo (dd02d18)
Some checks are pending
CI / build-and-test (push) Waiting to run
Some checks are pending
CI / build-and-test (push) Waiting to run
This commit is contained in:
parent
d6ce1ff1a3
commit
cbdfdeaa18
19
README.ja.md
19
README.ja.md
@ -12,6 +12,25 @@
|
|||||||
|
|
||||||
OpenAI 互換の LLM エンドポイント([Ollama](https://ollama.com/) / vLLM など)があれば単体で動作する。
|
OpenAI 互換の LLM エンドポイント([Ollama](https://ollama.com/) / vLLM など)があれば単体で動作する。
|
||||||
|
|
||||||
|
## スクリーンショット
|
||||||
|
|
||||||
|
3ペイン構成のワークスペース。左にタスク一覧、中央にライブの会話とレンダリング済み成果物、右に状態・ミッションブリーフ・ファイル・トレースをまとめたサイドパネル:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>ほかのスクリーンショット — タスク一覧・設定</summary>
|
||||||
|
|
||||||
|
実行中 / 待機中 / 完了をひと目で把握できるタスク一覧(クイックフィルタ付き):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
設定画面。`config.yaml` の各セクションがフォーム化(LLM ワーカー・サンドボックス・認証・ツールほか):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 主な機能
|
## 主な機能
|
||||||
|
|
||||||
- **タスク自動ルーティング** — タスク本文を LLM が分類し、最適な Piece(YAML ワークフロー)へ振り分け。
|
- **タスク自動ルーティング** — タスク本文を LLM が分類し、最適な Piece(YAML ワークフロー)へ振り分け。
|
||||||
|
|||||||
19
README.md
19
README.md
@ -12,6 +12,25 @@ English | [日本語](README.ja.md)
|
|||||||
|
|
||||||
It works standalone as long as you have an OpenAI-compatible LLM endpoint ([Ollama](https://ollama.com/) / vLLM, etc.).
|
It works standalone as long as you have an OpenAI-compatible LLM endpoint ([Ollama](https://ollama.com/) / vLLM, etc.).
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
The three-pane workspace — task list on the left, the live conversation and rendered output in the center, and a structured side panel (status, mission brief, files, trace) on the right:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>More screenshots — task list and settings</summary>
|
||||||
|
|
||||||
|
Task list with at-a-glance status (running / waiting / done) and quick filters:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Settings: every `config.yaml` section as a form — LLM workers, sandbox, auth, tools, and more:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Key features
|
## Key features
|
||||||
|
|
||||||
- **Automatic task routing** — the LLM classifies the task body and dispatches it to the best-fit Piece (a YAML workflow).
|
- **Automatic task routing** — the LLM classifies the task body and dispatches it to the best-fit Piece (a YAML workflow).
|
||||||
|
|||||||
BIN
docs/screenshots/settings.png
Normal file
BIN
docs/screenshots/settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
BIN
docs/screenshots/task-list.png
Normal file
BIN
docs/screenshots/task-list.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
BIN
docs/screenshots/workspace.png
Normal file
BIN
docs/screenshots/workspace.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 378 KiB |
28
package-lock.json
generated
28
package-lock.json
generated
@ -3349,17 +3349,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"hasown": "^2.0.2",
|
"hasown": "^2.0.4",
|
||||||
"mime-types": "^2.1.12"
|
"mime-types": "^2.1.35"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@ -3605,9 +3605,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@ -3617,9 +3617,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hono": {
|
"node_modules/hono": {
|
||||||
"version": "4.12.18",
|
"version": "4.12.25",
|
||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz",
|
||||||
"integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
|
"integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
@ -6787,9 +6787,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.20.1",
|
"version": "8.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||||
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
|
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
|
|||||||
58
src/bridge/security-headers.test.ts
Normal file
58
src/bridge/security-headers.test.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
import {
|
||||||
|
applySecurityHeaders,
|
||||||
|
securityHeadersMiddleware,
|
||||||
|
HSTS_MAX_AGE_SECONDS,
|
||||||
|
} from './security-headers.js';
|
||||||
|
import { type Request, type Response } from 'express';
|
||||||
|
|
||||||
|
function fakeRes() {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
const res = {
|
||||||
|
setHeader(name: string, value: string) {
|
||||||
|
headers[name] = value;
|
||||||
|
},
|
||||||
|
} as unknown as Response;
|
||||||
|
return { res, headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('applySecurityHeaders', () => {
|
||||||
|
it('sets the baseline hardening headers on every response', () => {
|
||||||
|
const { res, headers } = fakeRes();
|
||||||
|
applySecurityHeaders({ secure: false } as Request, res);
|
||||||
|
expect(headers['X-Content-Type-Options']).toBe('nosniff');
|
||||||
|
expect(headers['X-Frame-Options']).toBe('SAMEORIGIN');
|
||||||
|
expect(headers['Referrer-Policy']).toBe('strict-origin-when-cross-origin');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps X-Frame-Options at SAMEORIGIN so the app can serve its own iframes', () => {
|
||||||
|
// PDF preview and noVNC browser sessions embed same-origin iframes; DENY
|
||||||
|
// would break them.
|
||||||
|
const { res, headers } = fakeRes();
|
||||||
|
applySecurityHeaders({ secure: true } as Request, res);
|
||||||
|
expect(headers['X-Frame-Options']).not.toBe('DENY');
|
||||||
|
expect(headers['X-Frame-Options']).toBe('SAMEORIGIN');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits HSTS only when the response is served over TLS', () => {
|
||||||
|
const plain = fakeRes();
|
||||||
|
applySecurityHeaders({ secure: false } as Request, plain.res);
|
||||||
|
expect(plain.headers['Strict-Transport-Security']).toBeUndefined();
|
||||||
|
|
||||||
|
const tls = fakeRes();
|
||||||
|
applySecurityHeaders({ secure: true } as Request, tls.res);
|
||||||
|
expect(tls.headers['Strict-Transport-Security']).toBe(
|
||||||
|
`max-age=${HSTS_MAX_AGE_SECONDS}; includeSubDomains`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('securityHeadersMiddleware', () => {
|
||||||
|
it('applies the headers and calls next()', () => {
|
||||||
|
const { res, headers } = fakeRes();
|
||||||
|
const next = vi.fn();
|
||||||
|
securityHeadersMiddleware()({ secure: false } as Request, res, next);
|
||||||
|
expect(headers['X-Frame-Options']).toBe('SAMEORIGIN');
|
||||||
|
expect(next).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
});
|
||||||
45
src/bridge/security-headers.ts
Normal file
45
src/bridge/security-headers.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { type Request, type Response, type NextFunction } from 'express';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baseline security response headers applied to every response.
|
||||||
|
*
|
||||||
|
* These are deliberately conservative so they cannot break the SPA, which
|
||||||
|
* serves same-origin `<iframe>`s (PDF preview, noVNC browser sessions) and
|
||||||
|
* follows OAuth via top-level redirects rather than popups:
|
||||||
|
*
|
||||||
|
* - `X-Content-Type-Options: nosniff` — stop MIME-sniffing a response into
|
||||||
|
* something executable. (Untrusted workspace files layer `Content-Security-Policy:
|
||||||
|
* sandbox` on top via setUntrustedFileResponseHeaders.)
|
||||||
|
* - `X-Frame-Options: SAMEORIGIN` — block clickjacking from foreign origins
|
||||||
|
* while still allowing the app's own same-origin iframes.
|
||||||
|
* - `Referrer-Policy: strict-origin-when-cross-origin` — never leak the full
|
||||||
|
* path (which can carry task ids) to third-party origins.
|
||||||
|
* - `Strict-Transport-Security` — only when the response is served over TLS
|
||||||
|
* (native HTTPS, or a trusted proxy reporting `X-Forwarded-Proto: https`).
|
||||||
|
* Never emitted on plain-HTTP localhost dev so it can't pin a dev browser.
|
||||||
|
*
|
||||||
|
* Note: `req.secure` reflects `X-Forwarded-Proto` only when Express trusts the
|
||||||
|
* proxy. The server enables `trust proxy` together with `auth.secure_cookie`, so
|
||||||
|
* an operator terminating TLS at a reverse proxy should set `secure_cookie: true`
|
||||||
|
* to also get HSTS. Native HTTPS sets `req.secure` intrinsically and is unaffected.
|
||||||
|
*/
|
||||||
|
export const HSTS_MAX_AGE_SECONDS = 15552000; // 180 days
|
||||||
|
|
||||||
|
export function applySecurityHeaders(req: Request, res: Response): void {
|
||||||
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||||
|
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||||
|
if (req.secure) {
|
||||||
|
res.setHeader(
|
||||||
|
'Strict-Transport-Security',
|
||||||
|
`max-age=${HSTS_MAX_AGE_SECONDS}; includeSubDomains`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function securityHeadersMiddleware() {
|
||||||
|
return (req: Request, res: Response, next: NextFunction): void => {
|
||||||
|
applySecurityHeaders(req, res);
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ import { mountAdminApi } from './admin-api.js';
|
|||||||
import { createAdminGatewayApi } from './admin-gateway-api.js';
|
import { createAdminGatewayApi } from './admin-gateway-api.js';
|
||||||
import { mountUsersApi } from './users-api.js';
|
import { mountUsersApi } from './users-api.js';
|
||||||
import { mountShareApi } from './share-api.js';
|
import { mountShareApi } from './share-api.js';
|
||||||
|
import { securityHeadersMiddleware } from './security-headers.js';
|
||||||
import { mountLocalTasksApi } from './local-tasks-api.js';
|
import { mountLocalTasksApi } from './local-tasks-api.js';
|
||||||
import { findPieceFile } from './pieces-api.js';
|
import { findPieceFile } from './pieces-api.js';
|
||||||
import { mountLocalFilesApi } from './local-files-api.js';
|
import { mountLocalFilesApi } from './local-files-api.js';
|
||||||
@ -191,15 +192,11 @@ export function createCoreServer(opts: CoreServerOptions): {
|
|||||||
// Don't advertise the framework/version (minor info-leak, free to drop).
|
// Don't advertise the framework/version (minor info-leak, free to drop).
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
|
|
||||||
// Baseline hardening header on every response. `nosniff` stops the browser
|
// Baseline hardening headers on every response (nosniff, X-Frame-Options,
|
||||||
// from MIME-sniffing a response into something executable. File-serving
|
// Referrer-Policy, and HSTS over TLS). File-serving endpoints add
|
||||||
// endpoints add `Content-Security-Policy: sandbox` on top
|
// `Content-Security-Policy: sandbox` on top (see setUntrustedFileResponseHeaders)
|
||||||
// (see setUntrustedFileResponseHeaders) to neutralize stored XSS from
|
// to neutralize stored XSS from agent/user-authored workspace files.
|
||||||
// agent/user-authored workspace files.
|
app.use(securityHeadersMiddleware());
|
||||||
app.use((_req, res, next) => {
|
|
||||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// リバースプロキシ背後で secure cookie / X-Forwarded-Proto を正しく処理
|
// リバースプロキシ背後で secure cookie / X-Forwarded-Proto を正しく処理
|
||||||
if (opts.authConfig?.secureCookie) {
|
if (opts.authConfig?.secureCookie) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user