@goodandready/dsh-moa 0.2.47 → 0.2.48

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/moa-budget.js CHANGED
@@ -87,11 +87,31 @@ export function estimatePanelCost(judges = [], numCandidates = 1, prices = {}, m
87
87
  * Calculates accurate synthesis prompt tokens accounting for full user prompt,
88
88
  * judge criteria, and reference candidate texts (#158).
89
89
  */
90
- export function calcSynthesisPromptTokens(userPrompt = '', referenceOutputs = [], judgeCriteria = '', overhead = 2600) {
91
- const promptChars = (typeof userPrompt === 'string' ? userPrompt.length : 0) +
90
+ export function calcSynthesisPromptTokens(userPrompt = '', referenceOutputs = [], judgeCriteria = '', overhead = 2600, options = {}) {
91
+ const opts = (typeof overhead === 'object' && overhead !== null) ? overhead : (options || {})
92
+ const baseOverhead = typeof overhead === 'number' ? overhead : 2600
93
+ let promptChars = (typeof userPrompt === 'string' ? userPrompt.length : 0) +
92
94
  (typeof judgeCriteria === 'string' ? judgeCriteria.length : 0) +
93
- (Array.isArray(referenceOutputs) ? referenceOutputs.reduce((acc, r) => acc + (r.text?.length || 0), 0) : 0) +
94
- overhead
95
+ baseOverhead
96
+
97
+ if (Array.isArray(referenceOutputs)) {
98
+ for (const r of referenceOutputs) {
99
+ promptChars += (r.text?.length || 0)
100
+ if (r.testResult) {
101
+ promptChars += 150 + (r.testResult.output?.length || 0)
102
+ }
103
+ if (r.files && r.files.length > 0) {
104
+ promptChars += 50 + r.files.reduce((acc, f) => acc + (f.relativePath?.length || 0), 0)
105
+ }
106
+ }
107
+ }
108
+ if (opts.priorTurnBaseline) promptChars += opts.priorTurnBaseline.length + 50
109
+ if (opts.consensusReport) {
110
+ const crText = typeof opts.consensusReport === 'string' ? opts.consensusReport : JSON.stringify(opts.consensusReport)
111
+ promptChars += crText.length + 50
112
+ }
113
+ if (opts.compositeMergeDirective) promptChars += opts.compositeMergeDirective.length + 50
114
+
95
115
  return Math.max(50, Math.ceil(promptChars / 4))
96
116
  }
97
117
 
package/lib/moa-runner.js CHANGED
@@ -401,7 +401,15 @@ export async function runMoAPipeline({
401
401
  : null
402
402
 
403
403
  // 8. Synthesis phase via primary judge or fallback chain
404
- const synthPromptTokens = calcSynthesisPromptTokens(userPrompt, successfulRefs, judgeCriteria)
404
+ const synthesisPrompt = buildSynthesisPrompt(userPrompt, referenceOutputs, judgeCriteria, {
405
+ curatorSynthesis: isCuratorSynthesis,
406
+ blindEvaluation: isBlindEvaluation,
407
+ priorTurnBaseline,
408
+ compositeMergeDirective,
409
+ consensusReport: multiJudgeResult?.consensusReport,
410
+ })
411
+ const synthPromptTokens = Math.max(50, Math.ceil(synthesisPrompt.length / 4))
412
+
405
413
  if (preset?.budget_guard_enabled && preset?.max_budget_usd > 0 && !isFastMode) {
406
414
  const currentSpent = referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0) + (multiJudgeResult?.totalCostUsd || 0)
407
415
  const downNext = estimateDownstreamCost(primaryJudge, [], successfulRefs.length, prices, maxTokens, synthPromptTokens)
@@ -411,13 +419,6 @@ export async function runMoAPipeline({
411
419
  return createBudgetAbortPayload({ reason: phaseCheck.reason, primaryJudge, referenceOutputs, currentSpentUsd: currentSpent, preset, isRefinement, isFastMode, collectedCtx, startTime, multiJudgeResult })
412
420
  }
413
421
  }
414
- const synthesisPrompt = buildSynthesisPrompt(userPrompt, referenceOutputs, judgeCriteria, {
415
- curatorSynthesis: isCuratorSynthesis,
416
- blindEvaluation: isBlindEvaluation,
417
- priorTurnBaseline,
418
- compositeMergeDirective,
419
- consensusReport: multiJudgeResult?.consensusReport,
420
- })
421
422
  let synthesizedText = ''
422
423
  let aggUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 }
423
424
  let chosenJudge = primaryJudge
@@ -43,34 +43,47 @@ export function resolveCandidateDir(baseDir, candidateIndex, options = {}) {
43
43
  }
44
44
  }
45
45
 
46
- const COMPUTED_SHELL_EVASION = /(?:base64\s+(?:-d|--decode|-D)|\beval\s+["\']?\$|\bxxd\s+-r|\bprintf\s+[^|\n]+\|\s*(?:ba|z|da)?sh|\bopenssl\s+enc\s+-d)/i
46
+ export function decodeEscapedStrings(str) {
47
+ if (!str || typeof str !== 'string') return ''
48
+ let res = str.replace(/\\+/g, '\\').replace(/\\(?:([0-7]{1,3})|x([0-9a-fA-F]{2}))/g, (m, oct, hex) =>
49
+ oct ? String.fromCharCode(parseInt(oct, 8)) : hex ? String.fromCharCode(parseInt(hex, 16)) : m
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
+ }
61
+
62
+ 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)/i
47
63
 
48
- const UNIX_ROOT_PREFIX = /^\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run\/(?:user|systemd|lock|credentials|secrets|udev|initramfs|mount|shm|dbus|sshd|motd)|sbin|boot|Users\/(?:[a-zA-Z0-9_\-.]+)|Applications|Library|System|Volumes|private)(?:\/|$)/
64
+ 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)(?:\/|$)/
49
65
 
50
66
  /**
51
67
  * Validates that an executed script or command does not reference files outside allowed roots (#161, #225).
52
68
  */
53
69
  function scanPathTokensForLeak(text, allowedRoots, executionDir) {
54
70
  if (!text || typeof text !== 'string') return null
55
- const sanitized = text
56
- .replace(/https?:\/\/[^\s"'<>]+/gi, '')
57
- .replace(/\b(?:node|file|npm|data):[^\s"'<>]+/gi, '')
58
- const matches = sanitized.match(/(?:[a-zA-Z]:[\\/][^\s"'<>]+|(?<![\w.@\/\\])\/(?:[a-zA-Z0-9_\-.]+\/)*[a-zA-Z0-9_\-.]+|(?<![\w.@\/\\])\.\.[\\/][^\s"'<>]*)/g) || []
59
- for (const raw of matches) {
60
- if (/^\/(usr\/(bin|lib|include)|bin|lib|lib64|etc|dev)(\/|$)/.test(raw) && !/\.(txt|json|env|npmrc|key|ssh|md)/i.test(raw)) {
61
- continue
62
- }
63
- const isAbs = path.isAbsolute(raw)
64
- const isUp = raw.startsWith('..')
65
- if (!isAbs && !isUp) continue
66
- if (isAbs && process.platform !== 'win32' && !UNIX_ROOT_PREFIX.test(raw)) continue
67
- const resolved = path.resolve(executionDir, raw)
68
- const allowed = allowedRoots.some((r) => r && (resolved === r || resolved.startsWith(r + path.sep)))
69
- if (!allowed) {
70
- return raw
71
+ const checkText = (t) => {
72
+ const sanitized = t.replace(/https?:\/\/[^\s"'<>]+/gi, '').replace(/\b(?:node|file|npm|data):[^\s"'<>]+/gi, '')
73
+ const matches = sanitized.match(/(?:[a-zA-Z]:[\\/][^\s"'<>]+|(?<![\w.@\/\\])\/(?:[a-zA-Z0-9_\-.]+\/)*[a-zA-Z0-9_\-.]+|(?<![\w.@\/\\])\.\.[\\/][^\s"'<>]*)/g) || []
74
+ for (const raw of matches) {
75
+ if (/^\/(usr\/(bin|lib|include)|bin|lib|lib64|etc|dev)(\/|$)/.test(raw) && !/\.(txt|json|env|npmrc|key|ssh|md)/i.test(raw)) continue
76
+ const isAbs = path.isAbsolute(raw)
77
+ if ((!isAbs && !raw.startsWith('..')) || (isAbs && process.platform !== 'win32' && !UNIX_ROOT_PREFIX.test(raw))) continue
78
+ const resolved = path.resolve(executionDir, raw)
79
+ if (!allowedRoots.some((r) => r && (resolved === r || resolved.startsWith(r + path.sep)))) return raw
71
80
  }
81
+ return null
72
82
  }
73
- return null
83
+ const rawLeak = checkText(text)
84
+ if (rawLeak) return rawLeak
85
+ const decoded = decodeEscapedStrings(text)
86
+ return decoded !== text ? checkText(decoded) : null
74
87
  }
75
88
 
76
89
  /**
@@ -288,8 +301,17 @@ export async function runCandidateTestGate({
288
301
  const cp = require('node:child_process');
289
302
  const path = require('node:path');
290
303
  const allowed = ${JSON.stringify(allowedReads)};
291
- const COMPUTED_SHELL_EVASION = /(?:base64\\s+(?:-d|--decode|-D)|\\beval\\s+["\']?\\$|\\bxxd\\s+-r|\\bprintf\\s+[^|\\n]+\\|\\s*(?:ba|z|da)?sh|\\bopenssl\\s+enc\\s+-d)/i;
292
- const UNIX_ROOT_PREFIX = /^\\/(?:home|root|etc|var|tmp|opt|mnt|srv|media|proc|sys|dev|usr|bin|lib|lib64|run\\/(?:user|systemd|lock|credentials|secrets|udev|initramfs|mount|shm|dbus|sshd|motd)|sbin|boot|Users\\/(?:[a-zA-Z0-9_\\-.]+)|Applications|Library|System|Volumes|private)(?:\\/|$)/;
304
+ function decodeEscaped(str) {
305
+ if (!str || typeof str !== 'string') return '';
306
+ const norm = str.split(/\\+/).join(String.fromCharCode(92));
307
+ return norm.replace(new RegExp(String.fromCharCode(92, 92) + '(?:([0-7]{1,3})|x([0-9a-fA-F]{2}))', 'g'), (m, oct, hex) => {
308
+ if (oct) return String.fromCharCode(parseInt(oct, 8));
309
+ if (hex) return String.fromCharCode(parseInt(hex, 16));
310
+ return m;
311
+ });
312
+ }
313
+ 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)/i;
314
+ 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)(?:\\/|$)/;
293
315
  function checkSinglePath(p) {
294
316
  if (!p || typeof p !== 'string') return true;
295
317
  if (/^\\/(usr\\/(bin|lib|include)|bin|lib|lib64|etc|dev)(\\/|$)/.test(p) && !/\\.(txt|json|env|npmrc|key|ssh|md)/i.test(p)) return true;
@@ -303,10 +325,18 @@ function checkSinglePath(p) {
303
325
  function scanStringForPaths(str) {
304
326
  if (!str || typeof str !== 'string') return true;
305
327
  if (!checkSinglePath(str)) return false;
328
+ const dec = decodeEscaped(str);
329
+ if (dec !== str && !checkSinglePath(dec)) return false;
306
330
  const matches = str.match(/(?:[a-zA-Z]:[\\\\/][^\\s"'<>]+|(?<![\\w.@/\\\\])\\/(?:[a-zA-Z0-9_\\-.]+\\/)*[a-zA-Z0-9_\\-.]+|(?<![\\w.@/\\\\])\\.\\.[\\\\/][^\\s"'<>]*)/g) || [];
307
331
  for (const m of matches) {
308
332
  if (!checkSinglePath(m)) return false;
309
333
  }
334
+ if (dec !== str) {
335
+ const decMatches = dec.match(/(?:[a-zA-Z]:[\\\\/][^\\s"'<>]+|(?<![\\w.@/\\\\])\\/(?:[a-zA-Z0-9_\\-.]+\\/)*[a-zA-Z0-9_\\-.]+|(?<![\\w.@/\\\\])\\.\\.[\\\\/][^\\s"'<>]*)/g) || [];
336
+ for (const m of decMatches) {
337
+ if (!checkSinglePath(m)) return false;
338
+ }
339
+ }
310
340
  return true;
311
341
  }
312
342
  function guardArgs(cmd, args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-moa",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
4
4
  "description": "Mixture of Agents (MoA) plugin for DeepSeek Harness with /moa slash command",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",