@goodandready/dsh-moa 0.2.48 → 0.2.50
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/lib/history.js +63 -64
- package/lib/moa-runner.js +4 -1
- package/lib/moa-test-gate.js +27 -42
- package/lib/routes.js +1 -1
- package/lib/updater.js +3 -11
- package/package.json +1 -1
package/lib/history.js
CHANGED
|
@@ -51,6 +51,14 @@ export function invalidateHistoryCache() {
|
|
|
51
51
|
_historyCache = null
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function getOrInitCache(filePath, stat) {
|
|
55
|
+
if (!_historyCache || _historyCache.filePath !== filePath || _historyCache.mtimeMs !== stat.mtimeMs || _historyCache.size !== stat.size) {
|
|
56
|
+
_historyCache = { filePath, mtimeMs: stat.mtimeMs, size: stat.size, leaderboards: new Map(), runsById: new Map(), totalCount: null, fullyIndexed: false }
|
|
57
|
+
}
|
|
58
|
+
if (!_historyCache.runsById) _historyCache.runsById = new Map()
|
|
59
|
+
return _historyCache
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
/**
|
|
55
63
|
* Checks if the history file exceeds the size threshold and rotates it.
|
|
56
64
|
* Moves current file to `${filePath}.1`, removing any older `.1` backup.
|
|
@@ -277,22 +285,8 @@ export function countFileLinesSync(filePath, stat) {
|
|
|
277
285
|
try { fs.closeSync(fd) } catch { /* best effort */ }
|
|
278
286
|
}
|
|
279
287
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
_historyCache.filePath !== filePath ||
|
|
283
|
-
_historyCache.mtimeMs !== stat.mtimeMs ||
|
|
284
|
-
_historyCache.size !== stat.size
|
|
285
|
-
) {
|
|
286
|
-
_historyCache = {
|
|
287
|
-
filePath,
|
|
288
|
-
mtimeMs: stat.mtimeMs,
|
|
289
|
-
size: stat.size,
|
|
290
|
-
leaderboards: new Map(),
|
|
291
|
-
totalCount: count,
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
_historyCache.totalCount = count
|
|
295
|
-
}
|
|
288
|
+
const cache = getOrInitCache(filePath, stat)
|
|
289
|
+
cache.totalCount = count
|
|
296
290
|
return count
|
|
297
291
|
}
|
|
298
292
|
|
|
@@ -314,29 +308,17 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = getDefaultHisto
|
|
|
314
308
|
const lines = raw.trim().split('\n').filter(Boolean)
|
|
315
309
|
const total = lines.length
|
|
316
310
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
_historyCache.filePath !== filePath ||
|
|
320
|
-
_historyCache.mtimeMs !== stat.mtimeMs ||
|
|
321
|
-
_historyCache.size !== stat.size
|
|
322
|
-
) {
|
|
323
|
-
_historyCache = {
|
|
324
|
-
filePath,
|
|
325
|
-
mtimeMs: stat.mtimeMs,
|
|
326
|
-
size: stat.size,
|
|
327
|
-
leaderboards: new Map(),
|
|
328
|
-
totalCount: total,
|
|
329
|
-
}
|
|
330
|
-
} else {
|
|
331
|
-
_historyCache.totalCount = total
|
|
332
|
-
}
|
|
311
|
+
const cache = getOrInitCache(filePath, stat)
|
|
312
|
+
cache.totalCount = total
|
|
333
313
|
|
|
334
314
|
const sliced = lines
|
|
335
315
|
.slice(Math.max(0, total - offset - limit), total - offset)
|
|
336
316
|
.reverse()
|
|
337
317
|
.map((line) => {
|
|
338
318
|
try {
|
|
339
|
-
|
|
319
|
+
const item = JSON.parse(line)
|
|
320
|
+
if (item?.id) cache.runsById.set(item.id, item)
|
|
321
|
+
return item
|
|
340
322
|
} catch {
|
|
341
323
|
return null
|
|
342
324
|
}
|
|
@@ -348,24 +330,22 @@ export function getMoaHistory(limit = 20, offset = 0, filePath = getDefaultHisto
|
|
|
348
330
|
|
|
349
331
|
// For larger files: read only tail lines backwards to avoid splitting 10MB in memory
|
|
350
332
|
const tailLines = readTailLinesSync(filePath, needed)
|
|
333
|
+
const cache = getOrInitCache(filePath, stat)
|
|
351
334
|
const pageLines = tailLines.slice(offset, offset + limit)
|
|
352
335
|
const runs = pageLines
|
|
353
336
|
.map((line) => {
|
|
354
337
|
try {
|
|
355
|
-
|
|
338
|
+
const item = JSON.parse(line)
|
|
339
|
+
if (item?.id) cache.runsById.set(item.id, item)
|
|
340
|
+
return item
|
|
356
341
|
} catch {
|
|
357
342
|
return null
|
|
358
343
|
}
|
|
359
344
|
})
|
|
360
345
|
.filter(Boolean)
|
|
361
346
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
_historyCache.mtimeMs === stat.mtimeMs &&
|
|
365
|
-
_historyCache.size === stat.size &&
|
|
366
|
-
typeof _historyCache.totalCount === 'number')
|
|
367
|
-
? _historyCache.totalCount
|
|
368
|
-
: undefined
|
|
347
|
+
const isCached = _historyCache && _historyCache.filePath === filePath && _historyCache.mtimeMs === stat.mtimeMs && _historyCache.size === stat.size
|
|
348
|
+
let total = isCached && typeof _historyCache.totalCount === 'number' ? _historyCache.totalCount : undefined
|
|
369
349
|
if (typeof total !== 'number') {
|
|
370
350
|
total = tailLines.length < needed ? tailLines.length : countFileLinesSync(filePath, stat)
|
|
371
351
|
}
|
|
@@ -392,24 +372,9 @@ export function getMoaLeaderboard(filePath = getDefaultHistoryFile(), options =
|
|
|
392
372
|
const presetFilter = typeof options === 'string' ? options : (options?.presetFilter || options?.preset || null)
|
|
393
373
|
const filterKey = presetFilter || 'all'
|
|
394
374
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
_historyCache.mtimeMs === stat.mtimeMs &&
|
|
399
|
-
_historyCache.size === stat.size
|
|
400
|
-
) {
|
|
401
|
-
if (_historyCache.leaderboards.has(filterKey)) {
|
|
402
|
-
return _historyCache.leaderboards.get(filterKey)
|
|
403
|
-
}
|
|
404
|
-
} else {
|
|
405
|
-
_historyCache = {
|
|
406
|
-
filePath,
|
|
407
|
-
mtimeMs: stat.mtimeMs,
|
|
408
|
-
size: stat.size,
|
|
409
|
-
leaderboards: new Map(),
|
|
410
|
-
totalCount: null,
|
|
411
|
-
}
|
|
412
|
-
}
|
|
375
|
+
const isCached = _historyCache && _historyCache.filePath === filePath && _historyCache.mtimeMs === stat.mtimeMs && _historyCache.size === stat.size
|
|
376
|
+
if (isCached && _historyCache.leaderboards.has(filterKey)) return _historyCache.leaderboards.get(filterKey)
|
|
377
|
+
const cache = getOrInitCache(filePath, stat)
|
|
413
378
|
|
|
414
379
|
const raw = fs.readFileSync(filePath, 'utf8')
|
|
415
380
|
const lines = raw.trim().split('\n').filter(Boolean)
|
|
@@ -500,19 +465,53 @@ export function getMoaLeaderboard(filePath = getDefaultHistoryFile(), options =
|
|
|
500
465
|
}
|
|
501
466
|
|
|
502
467
|
/**
|
|
503
|
-
* Retrieves a single history run by ID.
|
|
468
|
+
* Retrieves a single history run by ID using in-memory cache and tail reading (#235).
|
|
504
469
|
*/
|
|
505
470
|
export function getMoaRunById(runId, filePath = getDefaultHistoryFile()) {
|
|
506
471
|
try {
|
|
507
|
-
if (!fs.existsSync(filePath)) return null
|
|
472
|
+
if (!runId || !fs.existsSync(filePath)) return null
|
|
473
|
+
const stat = fs.statSync(filePath)
|
|
474
|
+
const cache = getOrInitCache(filePath, stat)
|
|
475
|
+
if (cache.runsById.has(runId)) return cache.runsById.get(runId)
|
|
476
|
+
if (cache.fullyIndexed) return null
|
|
477
|
+
|
|
478
|
+
if (stat.size <= 65536) {
|
|
479
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
480
|
+
const lines = raw.trim().split(String.fromCharCode(10)).filter(Boolean)
|
|
481
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
482
|
+
try {
|
|
483
|
+
const item = JSON.parse(lines[i])
|
|
484
|
+
if (item?.id) cache.runsById.set(item.id, item)
|
|
485
|
+
} catch { /* skip corrupt */ }
|
|
486
|
+
}
|
|
487
|
+
cache.fullyIndexed = true
|
|
488
|
+
if (cache.runsById.has(runId)) return cache.runsById.get(runId)
|
|
489
|
+
cache.runsById.set(runId, null)
|
|
490
|
+
return null
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const tailLines = readTailLinesSync(filePath, 100)
|
|
494
|
+
for (const line of tailLines) {
|
|
495
|
+
try {
|
|
496
|
+
const item = JSON.parse(line)
|
|
497
|
+
if (item?.id) {
|
|
498
|
+
cache.runsById.set(item.id, item)
|
|
499
|
+
if (item.id === runId) return item
|
|
500
|
+
}
|
|
501
|
+
} catch { /* skip corrupt */ }
|
|
502
|
+
}
|
|
503
|
+
|
|
508
504
|
const raw = fs.readFileSync(filePath, 'utf8')
|
|
509
|
-
const lines = raw.trim().split(
|
|
505
|
+
const lines = raw.trim().split(String.fromCharCode(10)).filter(Boolean)
|
|
510
506
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
511
507
|
try {
|
|
512
508
|
const item = JSON.parse(lines[i])
|
|
513
|
-
if (item
|
|
514
|
-
} catch { /* corrupt
|
|
509
|
+
if (item?.id) cache.runsById.set(item.id, item)
|
|
510
|
+
} catch { /* skip corrupt */ }
|
|
515
511
|
}
|
|
512
|
+
cache.fullyIndexed = true
|
|
513
|
+
if (cache.runsById.has(runId)) return cache.runsById.get(runId)
|
|
514
|
+
cache.runsById.set(runId, null)
|
|
516
515
|
return null
|
|
517
516
|
} catch {
|
|
518
517
|
return null
|
package/lib/moa-runner.js
CHANGED
|
@@ -48,7 +48,7 @@ export async function runMoAPipeline({
|
|
|
48
48
|
const aggTemp = typeof preset?.aggregator_temperature === 'number' ? preset.aggregator_temperature : 0.4
|
|
49
49
|
const maxTokens = typeof preset?.max_tokens === 'number' ? preset.max_tokens : 4096
|
|
50
50
|
const judgeCriteria = preset?.judge_criteria || ''
|
|
51
|
-
|
|
51
|
+
let isFastMode = referenceModels.length === 1 && !preset?.curator_synthesis
|
|
52
52
|
const isCuratorSynthesis = Boolean(preset?.curator_synthesis), isStreamAggregator = preset?.stream_aggregator !== false
|
|
53
53
|
const isQuorumEnabled = Boolean(preset?.quorum_enabled), gracePeriodSec = typeof preset?.grace_period_sec === 'number' ? preset.grace_period_sec : 10
|
|
54
54
|
const candidateRetries = 1, refTimeoutSec = typeof preset?.reference_timeout_sec === 'number' ? preset.reference_timeout_sec : 60
|
|
@@ -93,6 +93,9 @@ export async function runMoAPipeline({
|
|
|
93
93
|
return createBudgetAbortPayload({ reason: budgetCheck.reason, primaryJudge, referenceOutputs: [], currentSpentUsd: 0, preset, isRefinement, isFastMode, collectedCtx, startTime })
|
|
94
94
|
}
|
|
95
95
|
const effectiveReferences = budgetCheck.references
|
|
96
|
+
if (effectiveReferences.length === 1 && !preset?.curator_synthesis) {
|
|
97
|
+
isFastMode = true
|
|
98
|
+
}
|
|
96
99
|
if (budgetCheck.action === 'trim' && typeof onProgress === 'function') {
|
|
97
100
|
onProgress(`✂️ *${budgetCheck.reason}*\n`)
|
|
98
101
|
}
|
package/lib/moa-test-gate.js
CHANGED
|
@@ -45,21 +45,12 @@ export function resolveCandidateDir(baseDir, candidateIndex, options = {}) {
|
|
|
45
45
|
|
|
46
46
|
export function decodeEscapedStrings(str) {
|
|
47
47
|
if (!str || typeof str !== 'string') return ''
|
|
48
|
-
|
|
48
|
+
return str.replace(/\\+/g, '\\').replace(/\\(?:([0-7]{1,3})|x([0-9a-fA-F]{2}))/g, (m, oct, hex) =>
|
|
49
49
|
oct ? String.fromCharCode(parseInt(oct, 8)) : hex ? String.fromCharCode(parseInt(hex, 16)) : m
|
|
50
50
|
)
|
|
51
|
-
return res.replace(/(?<![A-Za-z0-9+/=])(?:(?:[A-Za-z0-9+/]{4}){2,}|(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=))(?![A-Za-z0-9+/=])/g, (m) => {
|
|
52
|
-
try {
|
|
53
|
-
const dec = Buffer.from(m, 'base64').toString('utf8')
|
|
54
|
-
return /^[\x20-\x7E\r\n\t]+$/.test(dec) ? dec : m
|
|
55
|
-
} catch (err) {
|
|
56
|
-
logger.debug('[dsh-moa:testGate] base64 decode skip:', err?.message || err)
|
|
57
|
-
return m
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
51
|
}
|
|
61
52
|
|
|
62
|
-
const COMPUTED_SHELL_EVASION = /(?:base64\s+(?:-d|--decode|-D)[^|\n;]*\|\s*(?:ba|z|da)?sh|\beval\s+["
|
|
53
|
+
const COMPUTED_SHELL_EVASION = /(?:base64\s+(?:-d|--decode|-D)[^|\n;]*\|\s*(?:ba|z|da)?sh|\beval\s+["']?\$|\bxxd\s+-r[^|\n;]*\|\s*(?:ba|z|da)?sh|\bprintf\s+[^|\n]+\|\s*(?:ba|z|da)?sh|\bopenssl\s+enc\s+-d[^|\n;]*\|\s*(?:ba|z|da)?sh|\bprintf\s+(?:%s|["']%s["'])\s+.*["'](?:\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\$\(\s*(?:printf|echo)\s+.*["'](?:\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\x60\s*(?:printf|echo)\s+.*["'](?:\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\b(?:cat|head|tail|source|\.|touch|rm|cp|mv)\s+["']?\$[A-Za-z_]|>\s*["']?\$[A-Za-z_]|>>\s*["']?\$[A-Za-z_])/i
|
|
63
54
|
|
|
64
55
|
const UNIX_ROOT_PREFIX = /^\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run\/(?:user\/\d+|systemd|lock|credentials|secrets|udev|initramfs|mount|shm|dbus|sshd|motd)|sbin|boot|Users\/(?:[a-zA-Z0-9_\-.]+)|Applications|Library|System|Volumes|private)(?:\/|$)/
|
|
65
56
|
|
|
@@ -82,6 +73,12 @@ function scanPathTokensForLeak(text, allowedRoots, executionDir) {
|
|
|
82
73
|
}
|
|
83
74
|
const rawLeak = checkText(text)
|
|
84
75
|
if (rawLeak) return rawLeak
|
|
76
|
+
const segmented = text.match(/\bprintf\s+(?:%s|['"]%s['"])\s+((?:['"][^'"]*['"]\s*)+)/i)
|
|
77
|
+
if (segmented) {
|
|
78
|
+
const assembled = (segmented[1].match(/['"]([^'"]*)['"]/g) || []).map((s) => s.slice(1, -1)).join('')
|
|
79
|
+
const assembledLeak = checkText(assembled)
|
|
80
|
+
if (assembledLeak) return assembledLeak
|
|
81
|
+
}
|
|
85
82
|
const decoded = decodeEscapedStrings(text)
|
|
86
83
|
return decoded !== text ? checkText(decoded) : null
|
|
87
84
|
}
|
|
@@ -166,6 +163,18 @@ export async function runCandidateTestGate({
|
|
|
166
163
|
stderr = customRes?.stderr || ''
|
|
167
164
|
} else {
|
|
168
165
|
const allowedRoots = [executionDir, cwd, ...(candidateDir && candidateDir !== executionDir ? [candidateDir] : [])]
|
|
166
|
+
if (COMPUTED_SHELL_EVASION.test(resolvedCmd)) {
|
|
167
|
+
const durationMs = Date.now() - startTime
|
|
168
|
+
return {
|
|
169
|
+
candidateIndex,
|
|
170
|
+
passed: false,
|
|
171
|
+
exitCode: 1,
|
|
172
|
+
durationMs,
|
|
173
|
+
command: resolvedCmd,
|
|
174
|
+
output: 'Access denied: test command contains obfuscated or computed command execution',
|
|
175
|
+
summary: `FAIL (code 1, ${durationMs}ms)`,
|
|
176
|
+
}
|
|
177
|
+
}
|
|
169
178
|
const cmdTokens = resolvedCmd.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []
|
|
170
179
|
|
|
171
180
|
// 1. Check command tokens for absolute path leaks
|
|
@@ -310,7 +319,7 @@ function decodeEscaped(str) {
|
|
|
310
319
|
return m;
|
|
311
320
|
});
|
|
312
321
|
}
|
|
313
|
-
const COMPUTED_SHELL_EVASION = /(?:base64\\s+(?:-d|--decode|-D)[^|\\n;]*\\|\\s*(?:ba|z|da)?sh|\\beval\\s+["
|
|
322
|
+
const COMPUTED_SHELL_EVASION = /(?:base64\\s+(?:-d|--decode|-D)[^|\\n;]*\\|\\s*(?:ba|z|da)?sh|\\beval\\s+["']?\\$|\\bxxd\\s+-r[^|\\n;]*\\|\\s*(?:ba|z|da)?sh|\\bprintf\\s+[^|\\n]+\\|\\s*(?:ba|z|da)?sh|\\bopenssl\\s+enc\\s+-d[^|\\n;]*\\|\\s*(?:ba|z|da)?sh|\\bprintf\\s+(?:%s|["']%s["'])\\s+.*["'](?:\\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\\$\\(\\s*(?:printf|echo)\\s+.*["'](?:\\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\\x60\\s*(?:printf|echo)\\s+.*["'](?:\\/|mnt|home|tmp|etc|var|usr|root|opt)["']|\\b(?:cat|head|tail|source|\\.|touch|rm|cp|mv)\\s+["']?\\$[A-Za-z_]|>\\s*["']?\\$[A-Za-z_]|>>\\s*["']?\\$[A-Za-z_])/i;
|
|
314
323
|
const UNIX_ROOT_PREFIX = /^\\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run\\/(?:user\\/\\d+|systemd|lock|credentials|secrets|udev|initramfs|mount|shm|dbus|sshd|motd)|sbin|boot|Users\\/(?:[a-zA-Z0-9_\\-.]+)|Applications|Library|System|Volumes|private)(?:\\/|$)/;
|
|
315
324
|
function checkSinglePath(p) {
|
|
316
325
|
if (!p || typeof p !== 'string') return true;
|
|
@@ -355,36 +364,12 @@ function guardArgs(cmd, args) {
|
|
|
355
364
|
}
|
|
356
365
|
}
|
|
357
366
|
}
|
|
358
|
-
const origSpawn = cp.spawn;
|
|
359
|
-
cp.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
};
|
|
363
|
-
const
|
|
364
|
-
cp.spawnSync = function(cmd, args, opts) {
|
|
365
|
-
guardArgs(cmd, args);
|
|
366
|
-
return origSpawnSync.apply(this, arguments);
|
|
367
|
-
};
|
|
368
|
-
const origExecFile = cp.execFile;
|
|
369
|
-
cp.execFile = function(file, args, opts, cb) {
|
|
370
|
-
guardArgs(file, args);
|
|
371
|
-
return origExecFile.apply(this, arguments);
|
|
372
|
-
};
|
|
373
|
-
const origExecFileSync = cp.execFileSync;
|
|
374
|
-
cp.execFileSync = function(file, args, opts) {
|
|
375
|
-
guardArgs(file, args);
|
|
376
|
-
return origExecFileSync.apply(this, arguments);
|
|
377
|
-
};
|
|
378
|
-
const origExec = cp.exec;
|
|
379
|
-
cp.exec = function(cmd, opts, cb) {
|
|
380
|
-
guardArgs(cmd, [cmd]);
|
|
381
|
-
return origExec.apply(this, arguments);
|
|
382
|
-
};
|
|
383
|
-
const origExecSync = cp.execSync;
|
|
384
|
-
cp.execSync = function(cmd, opts) {
|
|
385
|
-
guardArgs(cmd, [cmd]);
|
|
386
|
-
return origExecSync.apply(this, arguments);
|
|
387
|
-
};
|
|
367
|
+
const origSpawn = cp.spawn; cp.spawn = function(cmd, args, opts) { guardArgs(cmd, args); return origSpawn.apply(this, arguments); };
|
|
368
|
+
const origSpawnSync = cp.spawnSync; cp.spawnSync = function(cmd, args, opts) { guardArgs(cmd, args); return origSpawnSync.apply(this, arguments); };
|
|
369
|
+
const origExecFile = cp.execFile; cp.execFile = function(file, args, opts, cb) { guardArgs(file, args); return origExecFile.apply(this, arguments); };
|
|
370
|
+
const origExecFileSync = cp.execFileSync; cp.execFileSync = function(file, args, opts) { guardArgs(file, args); return origExecFileSync.apply(this, arguments); };
|
|
371
|
+
const origExec = cp.exec; cp.exec = function(cmd, opts, cb) { guardArgs(cmd, [cmd]); return origExec.apply(this, arguments); };
|
|
372
|
+
const origExecSync = cp.execSync; cp.execSync = function(cmd, opts) { guardArgs(cmd, [cmd]); return origExecSync.apply(this, arguments); };
|
|
388
373
|
const fsMod = require('node:fs');
|
|
389
374
|
function guardFs(p, isWrite) {
|
|
390
375
|
if (!p || typeof p !== 'string') return;
|
package/lib/routes.js
CHANGED
|
@@ -512,7 +512,6 @@ ctx.effect(() => {
|
|
|
512
512
|
writeJson(res, 429, { ok: false, error: 'Rate limit exceeded: please wait before starting another run' })
|
|
513
513
|
return
|
|
514
514
|
}
|
|
515
|
-
_lastRunTimestamp = now
|
|
516
515
|
|
|
517
516
|
try {
|
|
518
517
|
const raw = await readBody(req)
|
|
@@ -522,6 +521,7 @@ ctx.effect(() => {
|
|
|
522
521
|
writeJson(res, 400, { ok: false, error: 'Empty prompt' })
|
|
523
522
|
return
|
|
524
523
|
}
|
|
524
|
+
_lastRunTimestamp = now
|
|
525
525
|
|
|
526
526
|
const cfg = live()
|
|
527
527
|
if (cfg.enabled === false) {
|
package/lib/updater.js
CHANGED
|
@@ -41,10 +41,11 @@ export function isTrustedUpdateRequest(request) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/** General safe write check for plugin write endpoints (CSRF / Cross-Site protection) */
|
|
44
|
+
/** General safe write check for plugin write endpoints (CSRF / Cross-Site protection, #234) */
|
|
45
45
|
export function isSafeWriteRequest(request) {
|
|
46
46
|
const remote = request?.socket?.remoteAddress || request?.connection?.remoteAddress;
|
|
47
47
|
const isLoop = !remote || isLoopback(remote);
|
|
48
|
+
if (!isLoop) return false;
|
|
48
49
|
|
|
49
50
|
const site = header(request, 'sec-fetch-site');
|
|
50
51
|
if (site === 'cross-site') return false;
|
|
@@ -52,13 +53,6 @@ export function isSafeWriteRequest(request) {
|
|
|
52
53
|
const origin = header(request, 'origin');
|
|
53
54
|
const host = header(request, 'host');
|
|
54
55
|
|
|
55
|
-
// If non-loopback caller, require valid same-origin browser proof
|
|
56
|
-
if (!isLoop) {
|
|
57
|
-
if (!origin || !host) return false;
|
|
58
|
-
if (site && site !== 'same-origin' && site !== 'none') return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Same-site without matching origin is rejected regardless of remote
|
|
62
56
|
if (site === 'same-site') {
|
|
63
57
|
if (!origin || !host) return false;
|
|
64
58
|
}
|
|
@@ -68,9 +62,7 @@ export function isSafeWriteRequest(request) {
|
|
|
68
62
|
try {
|
|
69
63
|
const url = new URL(origin);
|
|
70
64
|
if (host && url.host !== host) return false;
|
|
71
|
-
if (
|
|
72
|
-
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
|
|
73
|
-
}
|
|
65
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
|
|
74
66
|
} catch {
|
|
75
67
|
return false;
|
|
76
68
|
}
|