sync: update from private repo (dd02d18)
Some checks are pending
CI / build-and-test (push) Waiting to run

This commit is contained in:
oss-sync 2026-06-16 06:24:20 +00:00
parent d6ce1ff1a3
commit cbdfdeaa18
9 changed files with 161 additions and 23 deletions

View File

@ -12,6 +12,25 @@
OpenAI 互換の LLM エンドポイント([Ollama](https://ollama.com/) / vLLM など)があれば単体で動作する。 OpenAI 互換の LLM エンドポイント([Ollama](https://ollama.com/) / vLLM など)があれば単体で動作する。
## スクリーンショット
3ペイン構成のワークスペース。左にタスク一覧、中央にライブの会話とレンダリング済み成果物、右に状態・ミッションブリーフ・ファイル・トレースをまとめたサイドパネル:
![タスクワークスペース](docs/screenshots/workspace.png)
<details>
<summary>ほかのスクリーンショット — タスク一覧・設定</summary>
実行中 / 待機中 / 完了をひと目で把握できるタスク一覧(クイックフィルタ付き):
![タスク一覧](docs/screenshots/task-list.png)
設定画面。`config.yaml` の各セクションがフォーム化LLM ワーカー・サンドボックス・認証・ツールほか):
![設定](docs/screenshots/settings.png)
</details>
## 主な機能 ## 主な機能
- **タスク自動ルーティング** — タスク本文を LLM が分類し、最適な PieceYAML ワークフロー)へ振り分け。 - **タスク自動ルーティング** — タスク本文を LLM が分類し、最適な PieceYAML ワークフロー)へ振り分け。

View File

@ -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:
![Task workspace](docs/screenshots/workspace.png)
<details>
<summary>More screenshots — task list and settings</summary>
Task list with at-a-glance status (running / waiting / done) and quick filters:
![Task list](docs/screenshots/task-list.png)
Settings: every `config.yaml` section as a form — LLM workers, sandbox, auth, tools, and more:
![Settings](docs/screenshots/settings.png)
</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).

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

28
package-lock.json generated
View File

@ -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"

View 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();
});
});

View 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();
};
}

View File

@ -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) {