dodo-mcp 1.0.1 → 1.0.2
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/README.md +22 -5
- package/dist/cli/main.js +70 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/menu.js +10 -4
- package/dist/cli/menu.js.map +1 -1
- package/dist/server/appServer.js +70 -2
- package/dist/server/appServer.js.map +1 -1
- package/dist/server/configUi/app.js +19 -7
- package/dist/server/configUi/workbench.js +7 -4
- package/dist/server/ipcDispatch.js +25 -0
- package/dist/server/ipcDispatch.js.map +1 -1
- package/dist/server/remoteConfig.js +317 -0
- package/dist/server/remoteConfig.js.map +1 -0
- package/docs/AI_PROVIDERS.md +1 -1
- package/docs/ARCHITECTURE.md +17 -2
- package/docs/COMPATIBILITY.md +1 -1
- package/docs/MANUAL_ACCEPTANCE.md +25 -1
- package/docs/RELEASE_1.0.2.md +46 -0
- package/docs/RELEASE_NOTES.md +18 -0
- package/docs/SECURITY.md +18 -2
- package/docs/TEST_REPORT.md +29 -2
- package/docs/TUNNEL.md +42 -3
- package/docs/WINDOWS.md +1 -1
- package/docs/adr/047-temporary-remote-config.md +47 -0
- package/docs/adr/README.md +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import { DodoError } from '../errors.js';
|
|
5
|
+
const DEFAULT_LEASE_MS = 60 * 60 * 1000;
|
|
6
|
+
const PAIRING_TTL_MS = 10 * 60 * 1000;
|
|
7
|
+
const COOKIE_NAME = '__Secure-dodo_remote_config';
|
|
8
|
+
const REMOTE_CSP = "default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'self'";
|
|
9
|
+
const PAIR_ALPHABET = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
|
|
10
|
+
function digest(value) {
|
|
11
|
+
return createHash('sha256').update(value).digest();
|
|
12
|
+
}
|
|
13
|
+
function sameDigest(expected, value) {
|
|
14
|
+
if (!expected)
|
|
15
|
+
return false;
|
|
16
|
+
const actual = digest(value);
|
|
17
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
18
|
+
}
|
|
19
|
+
function pairingCode() {
|
|
20
|
+
const bytes = randomBytes(16);
|
|
21
|
+
const chars = [...bytes].map(byte => PAIR_ALPHABET[byte & 31]).join('');
|
|
22
|
+
return chars.match(/.{1,4}/g)?.join('-') ?? chars;
|
|
23
|
+
}
|
|
24
|
+
function cookie(req, name) {
|
|
25
|
+
const raw = req.headers.cookie ?? '';
|
|
26
|
+
for (const part of raw.split(';')) {
|
|
27
|
+
const item = part.trim();
|
|
28
|
+
if (!item.startsWith(`${name}=`))
|
|
29
|
+
continue;
|
|
30
|
+
const value = item.slice(name.length + 1);
|
|
31
|
+
if (/^[A-Za-z0-9_-]{20,200}$/.test(value))
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
function securityHeaders(res) {
|
|
37
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
38
|
+
res.setHeader('Pragma', 'no-cache');
|
|
39
|
+
res.setHeader('Referrer-Policy', 'no-referrer');
|
|
40
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
41
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
42
|
+
res.setHeader('Content-Security-Policy', REMOTE_CSP);
|
|
43
|
+
}
|
|
44
|
+
function safeHeader(req, name, max) {
|
|
45
|
+
const value = req.headers[name];
|
|
46
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > max || /[\r\n]/.test(value))
|
|
47
|
+
return undefined;
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
const LOGIN_HTML = Buffer.from(`<!doctype html>
|
|
51
|
+
<html lang="th" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark light"><meta name="referrer" content="no-referrer"><title>DODO Remote Config</title><link rel="stylesheet" href="/config/login.css"></head><body><main><section><div class="logo" aria-hidden="true">DD</div><h1>DODO Remote Config</h1><p>หน้าเจ้าของเครื่องแบบชั่วคราว กรุณากรอก pairing code ที่แสดงใน terminal ของ DODO</p><form id="pair" novalidate><label for="code">Pairing code</label><input id="code" name="code" type="text" autocomplete="one-time-code" spellcheck="false" maxlength="32" required autofocus><button type="submit">เชื่อมต่อ</button><p id="status" role="alert" aria-live="polite"></p></form><p class="note">Code ใช้ได้ครั้งเดียว หน้า Config จะปิดอัตโนมัติภายใน 1 ชั่วโมง และไม่มี credential อยู่ใน URL</p></section></main><script src="/config/login.js"></script></body></html>`);
|
|
52
|
+
const LOGIN_CSS = Buffer.from(`:root{color-scheme:dark;background:#0f141b;color:#e8edf4;font:16px/1.6 system-ui,-apple-system,"Noto Sans Thai",sans-serif}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px}main{width:min(100%,480px)}section{background:#171e28;border:1px solid #303a48;border-radius:14px;padding:28px;box-shadow:0 18px 45px #0005}.logo{display:grid;place-items:center;width:48px;height:48px;border-radius:10px;background:#2c78c8;color:white;font-weight:800}h1{margin:18px 0 4px;font-size:1.55rem}p{color:#b7c1ce}label{display:block;margin:20px 0 7px;font-weight:700}input,button{width:100%;min-height:46px;border-radius:9px;font:inherit}input{border:1px solid #526074;background:#0e141c;color:#fff;padding:10px 12px;text-transform:uppercase;letter-spacing:.08em}input:focus{outline:3px solid #4c9cff55;border-color:#65aaff}button{margin-top:12px;border:0;background:#2877c7;color:white;font-weight:750;cursor:pointer}button:disabled{opacity:.6;cursor:wait}#status{min-height:1.6em;color:#ff9e9e}.note{font-size:.9rem;color:#93a0b1}@media(prefers-color-scheme:light){:root{background:#eef2f7;color:#17202b}section{background:#fff;border-color:#ccd5e1;box-shadow:0 18px 45px #34405422}p{color:#566274}input{background:#fff;color:#17202b;border-color:#8794a6}.note{color:#657286}}`);
|
|
53
|
+
const LOGIN_JS = Buffer.from(`'use strict';(()=>{const form=document.getElementById('pair'),input=document.getElementById('code'),status=document.getElementById('status'),button=form.querySelector('button');form.addEventListener('submit',async event=>{event.preventDefault();if(button.disabled)return;status.textContent='';button.disabled=true;button.textContent='กำลังตรวจ…';try{const response=await fetch('/config/pair',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:input.value}),credentials:'same-origin',cache:'no-store'});let result={};try{result=await response.json()}catch{}if(!response.ok)throw new Error(result.error||('HTTP '+response.status));input.value='';location.replace('/config');}catch(error){input.value='';status.textContent='✕ '+error.message;input.focus();}finally{button.disabled=false;button.textContent='เชื่อมต่อ';}});})();`);
|
|
54
|
+
/**
|
|
55
|
+
* A process-only, one-hour bridge from the public listener to the existing
|
|
56
|
+
* loopback Local Config server. It grants no authority itself: after a
|
|
57
|
+
* one-time pairing exchange every request is re-authenticated here and then
|
|
58
|
+
* forwarded through Local Config's original capability and policy routes.
|
|
59
|
+
*/
|
|
60
|
+
export class RemoteConfigGateway {
|
|
61
|
+
publicOrigin;
|
|
62
|
+
defaultLeaseMs;
|
|
63
|
+
target;
|
|
64
|
+
pairingHash;
|
|
65
|
+
pairingExpiresAt = 0;
|
|
66
|
+
sessionHash;
|
|
67
|
+
expiresAt = 0;
|
|
68
|
+
attempts = 0;
|
|
69
|
+
attemptWindow = 0;
|
|
70
|
+
timer;
|
|
71
|
+
constructor(publicOrigin, defaultLeaseMs = DEFAULT_LEASE_MS) {
|
|
72
|
+
this.publicOrigin = publicOrigin;
|
|
73
|
+
this.defaultLeaseMs = defaultLeaseMs;
|
|
74
|
+
}
|
|
75
|
+
attachLocal(url) {
|
|
76
|
+
const parsed = new URL(url);
|
|
77
|
+
const capability = parsed.hash.slice(1);
|
|
78
|
+
if (parsed.protocol !== 'http:' || parsed.hostname !== '127.0.0.1' || !capability) {
|
|
79
|
+
throw new DodoError('INTERNAL_ERROR', 'Local Config target is not an owner-private loopback capability');
|
|
80
|
+
}
|
|
81
|
+
parsed.hash = '';
|
|
82
|
+
this.target = { origin: parsed.origin, capability };
|
|
83
|
+
}
|
|
84
|
+
open(leaseMs = this.defaultLeaseMs) {
|
|
85
|
+
if (!this.target)
|
|
86
|
+
throw new DodoError('NOT_SUPPORTED', 'Local Config is unavailable for this DODO process');
|
|
87
|
+
if (!Number.isSafeInteger(leaseMs) || leaseMs < 100 || leaseMs > DEFAULT_LEASE_MS) {
|
|
88
|
+
throw new DodoError('INVALID_INPUT', 'Remote Config lifetime must not exceed 1 hour');
|
|
89
|
+
}
|
|
90
|
+
this.close();
|
|
91
|
+
const code = pairingCode();
|
|
92
|
+
this.pairingHash = digest(code);
|
|
93
|
+
this.expiresAt = Date.now() + leaseMs;
|
|
94
|
+
this.pairingExpiresAt = Math.min(this.expiresAt, Date.now() + PAIRING_TTL_MS);
|
|
95
|
+
this.attemptWindow = Date.now();
|
|
96
|
+
this.timer = setTimeout(() => this.close(), leaseMs);
|
|
97
|
+
this.timer.unref();
|
|
98
|
+
return { url: `${this.publicOrigin}/config`, pairingCode: code, expiresAt: this.expiresAt };
|
|
99
|
+
}
|
|
100
|
+
close() {
|
|
101
|
+
if (this.timer)
|
|
102
|
+
clearTimeout(this.timer);
|
|
103
|
+
this.timer = undefined;
|
|
104
|
+
this.pairingHash = undefined;
|
|
105
|
+
this.sessionHash = undefined;
|
|
106
|
+
this.pairingExpiresAt = 0;
|
|
107
|
+
this.expiresAt = 0;
|
|
108
|
+
this.attempts = 0;
|
|
109
|
+
}
|
|
110
|
+
status() {
|
|
111
|
+
const active = this.active();
|
|
112
|
+
return {
|
|
113
|
+
active,
|
|
114
|
+
paired: active && Boolean(this.sessionHash),
|
|
115
|
+
url: `${this.publicOrigin}/config`,
|
|
116
|
+
expiresAt: active ? this.expiresAt : null,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
mount(app) {
|
|
120
|
+
app.get('/config/login.css', (req, res, next) => this.publicAsset(req, res, next, 'text/css; charset=utf-8', LOGIN_CSS));
|
|
121
|
+
app.get('/config/login.js', (req, res, next) => this.publicAsset(req, res, next, 'text/javascript; charset=utf-8', LOGIN_JS));
|
|
122
|
+
app.post('/config/pair', express.json({ limit: 2048 }), (req, res, next) => this.pair(req, res, next));
|
|
123
|
+
app.use('/config/pair', (error, _req, res, _next) => {
|
|
124
|
+
if (!this.active()) {
|
|
125
|
+
res.status(404).end();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
securityHeaders(res);
|
|
129
|
+
res.status(400).json({ error: 'Pairing request is invalid', code: 'INVALID_INPUT' });
|
|
130
|
+
void error;
|
|
131
|
+
});
|
|
132
|
+
app.get(['/config', '/config/'], (req, res, next) => {
|
|
133
|
+
if (!this.active()) {
|
|
134
|
+
next();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!this.authorized(req)) {
|
|
138
|
+
securityHeaders(res);
|
|
139
|
+
res.type('html').send(LOGIN_HTML);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.proxy(req, res, '/', true);
|
|
143
|
+
});
|
|
144
|
+
app.use('/config/assets', (req, res, next) => {
|
|
145
|
+
if (!this.active()) {
|
|
146
|
+
next();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (!this.authorized(req)) {
|
|
150
|
+
securityHeaders(res);
|
|
151
|
+
res.status(401).end();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (req.method !== 'GET') {
|
|
155
|
+
res.status(405).end();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.proxy(req, res, req.originalUrl.slice('/config'.length), false);
|
|
159
|
+
});
|
|
160
|
+
app.use('/config/api', (req, res, next) => {
|
|
161
|
+
if (!this.active()) {
|
|
162
|
+
next();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!this.authorized(req)) {
|
|
166
|
+
securityHeaders(res);
|
|
167
|
+
res.status(401).json({ error: 'Remote Config requires pairing from the owner terminal', code: 'REMOTE_CONFIG_AUTH_REQUIRED' });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!['GET', 'POST'].includes(req.method)) {
|
|
171
|
+
res.status(405).end();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const origin = req.headers.origin;
|
|
175
|
+
if ((origin && origin !== this.publicOrigin) || req.headers['sec-fetch-site'] === 'cross-site') {
|
|
176
|
+
securityHeaders(res);
|
|
177
|
+
res.status(403).end();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.proxy(req, res, req.originalUrl.slice('/config'.length), req.method === 'GET' && req.path === '/state');
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
active() {
|
|
184
|
+
if (this.expiresAt > Date.now())
|
|
185
|
+
return true;
|
|
186
|
+
if (this.expiresAt !== 0)
|
|
187
|
+
this.close();
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
authorized(req) {
|
|
191
|
+
return this.active() && sameDigest(this.sessionHash, cookie(req, COOKIE_NAME));
|
|
192
|
+
}
|
|
193
|
+
publicAsset(req, res, next, type, body) {
|
|
194
|
+
if (!this.active()) {
|
|
195
|
+
next();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
securityHeaders(res);
|
|
199
|
+
res.setHeader('Content-Type', type);
|
|
200
|
+
res.send(body);
|
|
201
|
+
}
|
|
202
|
+
pair(req, res, next) {
|
|
203
|
+
if (!this.active()) {
|
|
204
|
+
next();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
securityHeaders(res);
|
|
208
|
+
const origin = req.headers.origin;
|
|
209
|
+
if ((origin && origin !== this.publicOrigin) || req.headers['sec-fetch-site'] === 'cross-site') {
|
|
210
|
+
res.status(403).end();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const now = Date.now();
|
|
214
|
+
if (now - this.attemptWindow >= 60_000) {
|
|
215
|
+
this.attemptWindow = now;
|
|
216
|
+
this.attempts = 0;
|
|
217
|
+
}
|
|
218
|
+
this.attempts += 1;
|
|
219
|
+
if (this.attempts > 10) {
|
|
220
|
+
res.status(429).json({ error: 'Too many pairing attempts; run dodo --web to issue a new code', code: 'RATE_LIMITED' });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const code = req.body && typeof req.body.code === 'string' ? req.body.code.trim().toUpperCase() : '';
|
|
224
|
+
if (now > this.pairingExpiresAt || !sameDigest(this.pairingHash, code)) {
|
|
225
|
+
res.status(401).json({ error: 'Pairing code is invalid or expired', code: 'INVALID_PAIRING_CODE' });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.pairingHash = undefined; // one-time exchange
|
|
229
|
+
const session = randomBytes(32).toString('base64url');
|
|
230
|
+
this.sessionHash = digest(session);
|
|
231
|
+
const maxAge = Math.max(1, Math.floor((this.expiresAt - now) / 1000));
|
|
232
|
+
res.setHeader('Set-Cookie', `${COOKIE_NAME}=${session}; Path=/config; Max-Age=${maxAge}; HttpOnly; Secure; SameSite=Strict`);
|
|
233
|
+
res.json({ ok: true, expiresAt: this.expiresAt, redirect: '/config' });
|
|
234
|
+
}
|
|
235
|
+
proxy(req, res, upstreamPath, transform) {
|
|
236
|
+
const target = this.target;
|
|
237
|
+
if (!target) {
|
|
238
|
+
securityHeaders(res);
|
|
239
|
+
res.status(503).json({ error: 'Local Config is unavailable' });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const local = new URL(target.origin);
|
|
243
|
+
const headers = {
|
|
244
|
+
Host: local.host,
|
|
245
|
+
Authorization: `Bearer ${target.capability}`,
|
|
246
|
+
};
|
|
247
|
+
for (const [name, max] of [['content-type', 100], ['x-dodo-workspace', 256], ['x-dodo-epoch', 256]]) {
|
|
248
|
+
const value = safeHeader(req, name, max);
|
|
249
|
+
if (value)
|
|
250
|
+
headers[name] = value;
|
|
251
|
+
}
|
|
252
|
+
const length = safeHeader(req, 'content-length', 20);
|
|
253
|
+
if (length && /^\d+$/.test(length) && Number(length) <= 32 * 1024)
|
|
254
|
+
headers['content-length'] = length;
|
|
255
|
+
const upstream = http.request({
|
|
256
|
+
hostname: '127.0.0.1',
|
|
257
|
+
port: Number(local.port),
|
|
258
|
+
path: upstreamPath,
|
|
259
|
+
method: req.method,
|
|
260
|
+
headers,
|
|
261
|
+
}, response => {
|
|
262
|
+
const status = response.statusCode ?? 502;
|
|
263
|
+
const type = typeof response.headers['content-type'] === 'string' ? response.headers['content-type'] : undefined;
|
|
264
|
+
securityHeaders(res);
|
|
265
|
+
res.status(status);
|
|
266
|
+
if (type)
|
|
267
|
+
res.setHeader('Content-Type', type);
|
|
268
|
+
if (!transform || status !== 200) {
|
|
269
|
+
response.pipe(res);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const chunks = [];
|
|
273
|
+
let size = 0;
|
|
274
|
+
response.on('data', (chunk) => {
|
|
275
|
+
size += chunk.length;
|
|
276
|
+
if (size <= 2 * 1024 * 1024)
|
|
277
|
+
chunks.push(chunk);
|
|
278
|
+
});
|
|
279
|
+
response.on('end', () => {
|
|
280
|
+
if (size > 2 * 1024 * 1024) {
|
|
281
|
+
res.status(502).json({ error: 'Remote Config response exceeded its bound' });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
let body = Buffer.concat(chunks);
|
|
285
|
+
if (upstreamPath === '/') {
|
|
286
|
+
body = Buffer.from(body.toString('utf8').replaceAll('"/assets/', '"/config/assets/'));
|
|
287
|
+
}
|
|
288
|
+
else if (upstreamPath.startsWith('/api/state')) {
|
|
289
|
+
try {
|
|
290
|
+
const parsed = JSON.parse(body.toString('utf8'));
|
|
291
|
+
parsed.connection ??= {};
|
|
292
|
+
parsed.connection['expiresAt'] = this.expiresAt;
|
|
293
|
+
parsed.connection['remoteConfig'] = { active: true, expiresAt: this.expiresAt };
|
|
294
|
+
body = Buffer.from(JSON.stringify(parsed));
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
res.status(502).json({ error: 'Local Config returned invalid state' });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
res.send(body);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
upstream.once('error', () => {
|
|
305
|
+
if (!res.headersSent) {
|
|
306
|
+
securityHeaders(res);
|
|
307
|
+
res.status(502).json({ error: 'Local Config bridge is unavailable' });
|
|
308
|
+
}
|
|
309
|
+
else
|
|
310
|
+
res.end();
|
|
311
|
+
});
|
|
312
|
+
req.once('aborted', () => upstream.destroy());
|
|
313
|
+
req.pipe(upstream);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
export { DEFAULT_LEASE_MS as REMOTE_CONFIG_LEASE_MS };
|
|
317
|
+
//# sourceMappingURL=remoteConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remoteConfig.js","sourceRoot":"","sources":["../../src/server/remoteConfig.ts"],"names":[],"mappings":"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"}
|
package/docs/AI_PROVIDERS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AI Providers, Sub-agents และหลายโปรเจกต์
|
|
2
2
|
|
|
3
|
-
DODO MCP 1.0.
|
|
3
|
+
DODO MCP 1.0.2 สามารถเปิด runtime ของหลายโปรเจกต์ใน process เดียว และเรียกโมเดลจาก backend ผ่าน connection/profile ที่เจ้าของเลือกได้ ค่าเริ่มต้นเป็นโหมดส่วนตัว: เพิ่มโปรเจกต์, connection และ profile แล้วใช้งานได้ทันทีโดยไม่ต้องทำ allowlist ซ้ำ
|
|
4
4
|
|
|
5
5
|
## เริ่มใช้งานผ่านเว็บ
|
|
6
6
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -18,7 +18,10 @@ Whisper/interactive desktop remain explicitly separate gates.
|
|
|
18
18
|
|
|
19
19
|
## Overview
|
|
20
20
|
|
|
21
|
-
DODO แบ่งเป็น data plane สำหรับ MCP tools และ owner control plane สำหรับ Local
|
|
21
|
+
DODO แบ่งเป็น data plane สำหรับ MCP tools และ owner control plane สำหรับ Local
|
|
22
|
+
Config/CLI ทั้งสอง plane ใช้ state และ workspace lifecycle เดียวกัน Public listener
|
|
23
|
+
ไม่มี owner route ที่ active ตามค่าเริ่มต้น; ADR-047 เพิ่ม bounded `/config` bridge ที่
|
|
24
|
+
เจ้าของเปิดชั่วคราวและปิดเป็น 404 เมื่อ lease หมด
|
|
22
25
|
|
|
23
26
|
```text
|
|
24
27
|
AI client
|
|
@@ -42,6 +45,10 @@ Owner browser/CLI
|
|
|
42
45
|
▼
|
|
43
46
|
Local Config 127.0.0.1:21731 ──► WorkspaceHost ──► active workspace lifecycle
|
|
44
47
|
|
|
48
|
+
Tunnel ──► 127.0.0.1:21730/config ──► one-time pairing/session gate
|
|
49
|
+
└──────────► Local Config 127.0.0.1:21731
|
|
50
|
+
(lease ไม่เกิน 1 ชั่วโมง)
|
|
51
|
+
|
|
45
52
|
Local owner CLI ──► authenticated tunnel IPC ──► bounded cloudflared supervisor
|
|
46
53
|
|
|
47
54
|
Local owner CLI/Config ──► project registry ──► projectId + workspace reference
|
|
@@ -305,7 +312,15 @@ token ได้จาก hidden terminal prompt ระหว่าง `dodo star
|
|
|
305
312
|
Config endpoint Token ไม่เข้า trusted config หรือ OS credential provider Runtime ส่งค่า
|
|
306
313
|
ผ่าน `TUNNEL_TOKEN` ให้ child โดยตรง เก็บเพียงสถานะที่ไม่มี secret และปิด child ก่อน
|
|
307
314
|
DODO process จบ การเปลี่ยน workspace ไม่ย้ายหรือเพิ่มสิทธิ์ใด ๆ และ Local Config port
|
|
308
|
-
|
|
315
|
+
21731 ยังคงไม่รับ traffic จาก Tunnel
|
|
316
|
+
|
|
317
|
+
Remote Config ไม่เปลี่ยน bind ของ Local Config และไม่ copy admin handlers มาที่ public
|
|
318
|
+
app `RemoteConfigGateway` ถือ pairing/session digest กับ expiry ใน memory, proxy เฉพาะ
|
|
319
|
+
`/config`, `/config/assets/*`, `/config/api/*` และส่ง request ไป loopback owner server
|
|
320
|
+
ด้วย internal capability เดิม CLI เปิด/ต่ออายุผ่าน authenticated installation IPC;
|
|
321
|
+
หาก process เริ่มแบบ local-only IPC callback เริ่ม `TunnelRuntime` ด้วย run-scoped token
|
|
322
|
+
ก่อนออก lease การหมดอายุล้าง code/session และทำให้ namespace กลับเป็น 404 โดยไม่หยุด
|
|
323
|
+
MCP/OAuth/Tunnel
|
|
309
324
|
|
|
310
325
|
## Optional services
|
|
311
326
|
|
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| Windows | Deferred — มี candidate code/tests แต่ยังไม่ประกาศ native support จนกว่าจะผ่าน Windows 11 จริง |
|
|
11
11
|
| MCP HTTP | OAuth protected MCP endpoint ที่ `/mcp` |
|
|
12
12
|
| MCP STDIO | Full surface เป็นค่าเริ่มต้น |
|
|
13
|
-
| Local Config | loopback owner control
|
|
13
|
+
| Local Config | loopback owner control ที่ 21731; เปิด bounded Remote Config `/config` ผ่าน 21730 ได้ครั้งละไม่เกิน 1 ชั่วโมง |
|
|
14
14
|
| Cloudflare Tunnel | `dodo start`/Local Config ใช้ run-scoped token และ child `cloudflared`; Tunnel จริงยังเป็น manual gate |
|
|
15
15
|
| Package | `dodo-mcp` |
|
|
16
16
|
| CLI | `dodo` |
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
สถานะล่าสุดของ owner-state setup/import gate: `MANUAL_NOT_RUN` (2026-09-14) ชุด automated ใช้ fixture แยกและไม่แตะ config/OAuth/tunnel ของผู้ใช้
|
|
6
6
|
|
|
7
|
-
สถานะ Cloudflare
|
|
7
|
+
สถานะ live Cloudflare connection smoke: `MANUAL_PASS` (2026-09-15) บน macOS arm64,
|
|
8
|
+
Linux x64 และ Windows x64 โดยใช้ run-scoped secret ชั่วคราว; ทั้งสาม platform รายงาน
|
|
9
|
+
connected, stability 3 วินาที, clean stop และ credentialPersisted=false ใน
|
|
10
|
+
[GitHub run 34908481066](https://github.com/arthittakun/dodo-mcp/actions/runs/34908481066)
|
|
11
|
+
การตรวจ public DNS → MCP/OAuth และ Remote Config ผ่าน hostname จริงยัง
|
|
12
|
+
`MANUAL_NOT_RUN`; connection smoke ไม่ได้อ้างว่า AI client เชื่อมต่อแล้ว
|
|
8
13
|
|
|
9
14
|
## Cloudflare Tunnel
|
|
10
15
|
|
|
@@ -21,6 +26,25 @@
|
|
|
21
26
|
11. เริ่ม tunnel ใหม่แล้วหยุด DODO; ยืนยัน child หยุดตาม
|
|
22
27
|
12. บันทึก macOS และ Linux Docker แยก environment; Windows คง `MANUAL_NOT_RUN` จนถึง phase สุดท้าย
|
|
23
28
|
|
|
29
|
+
## Remote Config ผ่าน Tunnel
|
|
30
|
+
|
|
31
|
+
Automated Chromium fixture ผ่านแล้วทั้ง 1440×900 และ 390×844 โดยใช้ isolated
|
|
32
|
+
loopback public-origin fixture: จับคู่, dashboard/assets/API, workspace-bound mutation,
|
|
33
|
+
หมดอายุและปิดเป็น 404 ทำงานจริง การตรวจผ่าน public Cloudflare hostname จริงยัง
|
|
34
|
+
`MANUAL_NOT_RUN`
|
|
35
|
+
|
|
36
|
+
1. เริ่ม local-only ด้วย `dodo start --no-tunnel`
|
|
37
|
+
2. จาก terminal อื่นรัน `dodo --web` และกรอก run-scoped Tunnel token แบบซ่อน
|
|
38
|
+
3. ตรวจว่า process เดิมไม่ restart, MCP/OAuth ยังใช้ epoch เดิม และ cloudflared เริ่มใน process เดิม
|
|
39
|
+
4. เปิด URL `/config` จากอีกอุปกรณ์ ตรวจว่า URL ไม่มี query/fragment secret
|
|
40
|
+
5. กรอก pairing code ครั้งเดียว; ใช้ซ้ำต้องถูกปฏิเสธ
|
|
41
|
+
6. ตรวจ browser cookie เป็น Secure/HttpOnly/SameSite=Strict และ Path `/config`
|
|
42
|
+
7. เปลี่ยนค่าที่ไม่กระทบข้อมูลจริงใน fixture แล้วตรวจ workspace ID/epoch binding
|
|
43
|
+
8. รัน `dodo --web` อีกครั้ง ตรวจ session เดิมถูกยกเลิกและได้ code ใหม่
|
|
44
|
+
9. รัน `dodo web --close` ตรวจ `/config`, assets และ API เป็น 404 แต่ `/healthz`, OAuth และ `/mcp` ยังอยู่
|
|
45
|
+
10. เปิดใหม่แล้วรอ 1 ชั่วโมง ตรวจ namespace ปิดเองโดยไม่หยุด MCP/Tunnel
|
|
46
|
+
11. ตรวจ config, audit, tunnel log, browser local/session storage และ process argv ว่าไม่มี Tunnel token, pairing code หรือ session token
|
|
47
|
+
|
|
24
48
|
## Setup foundation
|
|
25
49
|
|
|
26
50
|
1. ใช้ config directory ชั่วคราวและรัน `dodo setup --check` กับ `dodo setup --plan`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# DODO MCP 1.0.2
|
|
2
|
+
|
|
3
|
+
Patch release นี้เพิ่ม Temporary Remote Config สำหรับเจ้าของที่ต้องการเปิดหน้า Config
|
|
4
|
+
ผ่าน Cloudflare Tunnel ชั่วคราว โดยคง Local Config ที่พอร์ต 21731 ไว้บน loopback และ
|
|
5
|
+
คง MCP/OAuth ที่พอร์ต 21730 ตามเดิม
|
|
6
|
+
|
|
7
|
+
## สิ่งที่เปลี่ยน
|
|
8
|
+
|
|
9
|
+
- เพิ่ม `dodo --web`, `dodo web --status` และ `dodo web --close`
|
|
10
|
+
- เปิด `/config` บน public listener ครั้งละไม่เกินหนึ่งชั่วโมงและปิดเป็น 404 เมื่อไม่มี
|
|
11
|
+
lease, หลังปิด หรือเมื่อหมดอายุ
|
|
12
|
+
- ใช้ pairing code แบบครั้งเดียวกับ session cookie ที่เป็น Secure, HttpOnly,
|
|
13
|
+
SameSite=Strict และจำกัด path ที่ `/config`
|
|
14
|
+
- ต่ออายุ Remote Config ผ่าน authenticated owner IPC ได้โดยไม่ restart MCP
|
|
15
|
+
- รองรับการส่ง temporary Cloudflare Tunnel token ไปยัง process ที่รันอยู่ผ่าน private
|
|
16
|
+
IPC โดยไม่เก็บ token ลง config, audit, URL หรือ command arguments
|
|
17
|
+
- เพิ่มเมนู Temporary Remote Config ใน `dodo --cli` พร้อม security, integration,
|
|
18
|
+
browser และ packaging coverage
|
|
19
|
+
|
|
20
|
+
## ขอบเขตความปลอดภัย
|
|
21
|
+
|
|
22
|
+
Remote Config ไม่เพิ่ม OAuth scope, project ACL, trust หรือ approval และไม่เพิ่ม MCP
|
|
23
|
+
tool สำหรับเปิดหน้า owner เส้นทาง public จะ proxy ไปยัง Local Config ด้วย capability
|
|
24
|
+
ภายใน process หลังผ่าน pairing/session เท่านั้น พร้อมคง Host/Origin, workspace/epoch,
|
|
25
|
+
validation, policy และ audit ของ Local Config
|
|
26
|
+
|
|
27
|
+
## ติดตั้งและอัปเดต
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g dodo-mcp@1.0.2
|
|
31
|
+
dodo --version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
หลังอัปเดตให้ restart DODO process ที่กำลังรันอยู่ จากนั้นใช้ `dodo --web` เมื่อ public
|
|
35
|
+
origin และ tunnel route พร้อมใช้งาน
|
|
36
|
+
|
|
37
|
+
## ผลตรวจ
|
|
38
|
+
|
|
39
|
+
- `npm run test:all`: **AUTOMATED_PASS** — 677 passed, 34 skipped, 0 failed;
|
|
40
|
+
packaging 16/16
|
|
41
|
+
- Chromium desktop 1440×900 และ mobile 390×844: **AUTOMATED_PASS** ใน fixture
|
|
42
|
+
- `npm audit --omit=dev`: **AUTOMATED_PASS** — 0 vulnerabilities
|
|
43
|
+
- public Cloudflare hostname → Remote Config จากอุปกรณ์ภายนอก: **MANUAL_NOT_RUN**
|
|
44
|
+
|
|
45
|
+
ผลจาก automated fixture ไม่ยืนยันสถานะ DNS, Tunnel หรือการเชื่อมต่อจาก AI client
|
|
46
|
+
ของเจ้าของ
|
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# DODO MCP — Release Notes
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Temporary Remote Config
|
|
6
|
+
|
|
7
|
+
- เพิ่ม `dodo --web`, `dodo web --status` และ `dodo web --close` สำหรับเปิดหน้า Config
|
|
8
|
+
ผ่าน public Tunnel ที่พอร์ต 21730 ครั้งละไม่เกิน 1 ชั่วโมง โดยพอร์ต 21731 ยังคง bind
|
|
9
|
+
loopback เท่านั้น
|
|
10
|
+
- ใช้ pairing code แบบครั้งเดียวและ Secure/HttpOnly/SameSite cookie ที่จำกัด path
|
|
11
|
+
`/config`; route ปิดเป็น 404 ก่อนเปิด หลังปิด และเมื่อหมดอายุ
|
|
12
|
+
- process ที่เริ่ม local-only สามารถรับ run-scoped Tunnel token ผ่าน authenticated
|
|
13
|
+
installation IPC โดยไม่ restart MCP และไม่บันทึก credential
|
|
14
|
+
- เพิ่มเมนู Remote Config ใน `dodo --cli`, security/integration/Chromium tests และ
|
|
15
|
+
ADR-047 โดยไม่เปลี่ยน OAuth, project authority, workspace context หรือ owner policy
|
|
16
|
+
|
|
17
|
+
ติดตั้งหรืออัปเดตด้วย `npm install -g dodo-mcp@1.0.2` จากนั้น restart process ที่กำลัง
|
|
18
|
+
รันอยู่ การเปิด Remote Config ผ่าน public hostname จริงยังต้องตรวจด้วย tunnel/DNS ของ
|
|
19
|
+
เจ้าของและไม่ถือว่าผ่านจาก automated fixture เพียงอย่างเดียว
|
|
20
|
+
|
|
3
21
|
## 1.0.1
|
|
4
22
|
|
|
5
23
|
### Platform และ release hardening
|
package/docs/SECURITY.md
CHANGED
|
@@ -47,7 +47,19 @@ request ที่อ้าง target ไม่มี ACL ถูกปฏิเ
|
|
|
47
47
|
|
|
48
48
|
MCP และ public route ต้องผ่าน OAuth เสมอ ห้ามใช้ localhost เป็น authentication, ห้ามเปิด CORS เป็น `*`, ห้ามส่ง token ใน query string และห้าม trust proxy headers จากภายนอก
|
|
49
49
|
|
|
50
|
-
Local Config bind loopback ใช้ private capability token, expiration, Host/Origin checks,
|
|
50
|
+
Local Config bind loopback ใช้ private capability token, expiration, Host/Origin checks,
|
|
51
|
+
forwarded-header rejection และ rate limit Public listener ไม่มี owner route ที่ active
|
|
52
|
+
ตามค่าเริ่มต้น เมื่อเจ้าของสั่ง `dodo --web` จะเปิดเฉพาะ namespace `/config` แบบ
|
|
53
|
+
process-memory lease ไม่เกิน 1 ชั่วโมง และกลับเป็น 404 เมื่อปิดหรือหมดอายุ
|
|
54
|
+
|
|
55
|
+
Remote Config ใช้ pairing code สุ่มแบบใช้ครั้งเดียว เก็บใน server เฉพาะ SHA-256 digest
|
|
56
|
+
และแลกเป็น session token ที่ server เก็บเฉพาะ digest Cookie เป็น `Secure`, `HttpOnly`,
|
|
57
|
+
`SameSite=Strict`, `Path=/config`; code/session ไม่อยู่ใน URL, config, browser storage,
|
|
58
|
+
log หรือ audit Proxy ส่งต่อไป Local Config ด้วย capability ภายใน และลอก client
|
|
59
|
+
Authorization/Cookie/Origin/Cloudflare/forwarded headers ออกก่อน ทุก mutation จึงยัง
|
|
60
|
+
ต้องผ่าน workspace/epoch binding และ owner API validation เดิม มี rate limit ที่
|
|
61
|
+
pairing endpoint และ CSP/no-store/frame denial บนทุก response ไม่มี MCP operation เปิด
|
|
62
|
+
หรือยืด lease ได้; เฉพาะ authenticated private IPC/CLI ของเจ้าของเท่านั้น
|
|
51
63
|
|
|
52
64
|
UI assets เสิร์ฟจากแพ็กเกจเองทั้งหมดรวมถึง SweetAlert2 ที่ vendor แบบ pin version (ไม่มี CDN; CSP ยังเป็น `script-src 'self'; style-src 'self'` โดยไม่มี `unsafe-inline`/`unsafe-eval`) เส้นทาง `/assets` เป็น fail-closed: path ที่ decode แล้วต้องตรงรูปแบบเข้มงวด (ลึกได้หนึ่งระดับเฉพาะ `ui/`, `vendor/`), นามสกุลอยู่ใน allowlist (`.css/.js/.svg`), ปฏิเสธ `..`, backslash, NUL และไฟล์ที่ resolve ออกนอก UI directory ทั้งหมดตอบ 404 ข้อความจาก server/paths/client/provider ทุกตัว render ผ่าน `textContent` (SweetAlert ใช้ `titleText`/`text` เท่านั้น ไม่ใช้ `html`) กล่องยืนยันของ UI เป็นชั้น UX เพิ่มเติม ไม่แทนที่ approval/validation ฝั่ง server
|
|
53
65
|
|
|
@@ -220,7 +232,11 @@ Response/config/audit เก็บเฉพาะสถานะที่ไม
|
|
|
220
232
|
ควบคุม tunnel หน้าเว็บเริ่ม network process เฉพาะเมื่อ owner กด “เปิด Tunnel รอบนี้”
|
|
221
233
|
และการตั้ง startup preference ไม่เริ่ม network process
|
|
222
234
|
|
|
223
|
-
Tunnel route ต้องชี้ทุก public path ไป MCP/OAuth listener `127.0.0.1:21730` เท่านั้น
|
|
235
|
+
Tunnel route ต้องชี้ทุก public path ไป MCP/OAuth listener `127.0.0.1:21730` เท่านั้น
|
|
236
|
+
Local Config `21731`, metrics `21732` และ private IPC ไม่ถูก expose `/config` ที่
|
|
237
|
+
listener 21730 เป็น bounded bridge ที่ปิดเป็น 404 โดยค่าเริ่มต้นและไม่ทำให้พอร์ต 21731
|
|
238
|
+
รับ network ภายนอก readiness บอกสถานะ Cloudflare connection เท่านั้น ไม่ใช่หลักฐานว่า
|
|
239
|
+
AI client กำลังเชื่อมต่อ
|
|
224
240
|
|
|
225
241
|
## Setup and existing-state import
|
|
226
242
|
|
package/docs/TEST_REPORT.md
CHANGED
|
@@ -2,7 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## Scope
|
|
4
4
|
|
|
5
|
-
รายงานนี้ใช้กับ DODO MCP 1.0.
|
|
5
|
+
รายงานนี้ใช้กับ DODO MCP 1.0.2 source และแยกผล automated กับ manual อย่างชัดเจน
|
|
6
|
+
|
|
7
|
+
## Release 1.0.2 — Temporary Remote Config
|
|
8
|
+
|
|
9
|
+
เพิ่ม owner-paired `/config` bridge บน listener 21730 ที่ปิดเป็น 404 ตามค่าเริ่มต้น
|
|
10
|
+
และเปิดได้ครั้งละไม่เกินหนึ่งชั่วโมงด้วย `dodo --web` โดย Local Config 21731 ยังคง
|
|
11
|
+
bind loopback เท่านั้น `dodo --web` ต่ออายุผ่าน authenticated installation IPC โดย
|
|
12
|
+
ไม่ restart MCP และเริ่ม process-owned Tunnel ด้วย token ชั่วคราวเมื่อ process เดิม
|
|
13
|
+
เริ่มแบบ local-only
|
|
14
|
+
|
|
15
|
+
- typecheck และ targeted lint: **AUTOMATED_PASS**
|
|
16
|
+
- `tests/security/remoteConfig.test.ts`: **AUTOMATED_PASS** — absent-by-default,
|
|
17
|
+
one-time pairing, strict cookie/CSP, unauthorized asset/API, cross-site denial,
|
|
18
|
+
workspace/epoch binding, expiry/close, IPC renewal, token/pairing non-persistence
|
|
19
|
+
และ run-scoped Tunnel handoff
|
|
20
|
+
- `tests/integration/remoteConfigUi.test.ts`: **AUTOMATED_PASS** ใน Chromium จริง —
|
|
21
|
+
pairing และ dashboard จริงที่ 1440×900/390×844, no console errors, no horizontal
|
|
22
|
+
overflow, close → 404; screenshots อยู่ใน ignored local release evidence
|
|
23
|
+
- `tests/integration/cli.test.ts`: bounded `--web`, `web --status`, `web --close`
|
|
24
|
+
contract ผ่าน
|
|
25
|
+
- `npm run test:all`: **AUTOMATED_PASS** — 98 files ผ่าน, 3 files ข้ามตาม platform;
|
|
26
|
+
677 tests ผ่าน, 34 skipped, 0 failed และ packaging 16/16
|
|
27
|
+
- `npm audit --omit=dev`: **AUTOMATED_PASS** — 0 vulnerabilities
|
|
28
|
+
- npm pack manifest: **AUTOMATED_PASS** — รวม `dist/server/remoteConfig.js`, Local
|
|
29
|
+
Config UI, ADR-047 และ release note; release evidence/state/credentials ไม่อยู่ใน manifest
|
|
30
|
+
- public Cloudflare hostname → Remote Config บนอุปกรณ์ภายนอก: **MANUAL_NOT_RUN**
|
|
6
31
|
|
|
7
32
|
## Release 1.0.1 platform gate — 2026-09-15
|
|
8
33
|
|
|
@@ -322,7 +347,9 @@ candidate บน Node 22/24 โดยไม่รับ untrusted pull requests
|
|
|
322
347
|
|
|
323
348
|
สถานะ manual setup/import บน owner state จริง: `MANUAL_NOT_RUN` การตรวจรับใช้ isolated fixtures และ temporary package เท่านั้น ไม่มีการเปลี่ยน owner config, OAuth state, tunnel, DNS หรือ global installation
|
|
324
349
|
|
|
325
|
-
สถานะ
|
|
350
|
+
สถานะ live Cloudflare connection/clean-stop smoke บน macOS/Windows/Linux:
|
|
351
|
+
`MANUAL_PASS` (2026-09-15, run 34908481066); public DNS → MCP/OAuth/Remote Config
|
|
352
|
+
end-to-end บนอุปกรณ์ภายนอก: `MANUAL_NOT_RUN`
|
|
326
353
|
|
|
327
354
|
สถานะ multi-project federation ผ่าน external AI และ owner project จริง: `MANUAL_NOT_RUN`
|
|
328
355
|
|