@goodandready/dsh-goal 0.2.7 → 0.2.9

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/client.js CHANGED
@@ -4685,7 +4685,7 @@ window.__ModuleLoader__.load({
4685
4685
 
4686
4686
  };
4687
4687
 
4688
- module.exports.inject = ['slots', 'locale', 'settingsScope'];
4688
+ module.exports.inject = ['slots', 'locale', 'configForms'];
4689
4689
 
4690
4690
  module.exports.apply = function apply(ctx) {
4691
4691
  if (typeof ctx.effect === 'function') {
@@ -4694,9 +4694,9 @@ window.__ModuleLoader__.load({
4694
4694
  ctx.locale?.register?.(NS, LOCALES);
4695
4695
  } catch (err) { /* handled defensively */ }
4696
4696
 
4697
- const settingsScope =
4698
- ctx.settingsScope && typeof ctx.settingsScope.bind === 'function'
4699
- ? ctx.settingsScope.bind({ namespace: NS })
4697
+ const configForms =
4698
+ ctx.configForms && typeof ctx.configForms.get === 'function'
4699
+ ? ctx.configForms.get(NS)
4700
4700
  : null;
4701
4701
 
4702
4702
  const disposers = [];
@@ -4736,7 +4736,7 @@ window.__ModuleLoader__.load({
4736
4736
  order: 60,
4737
4737
  label: () => 'Goal',
4738
4738
  locale: NS,
4739
- inject: () => ({ ctx, scope: settingsScope }),
4739
+ inject: () => ({ ctx, scope: configForms }),
4740
4740
  },
4741
4741
  GoalSettingsCard,
4742
4742
  );
@@ -4747,7 +4747,7 @@ window.__ModuleLoader__.load({
4747
4747
  name: 'plugins.row.config',
4748
4748
  key: ROW_CONFIG_KEY,
4749
4749
  locale: NS,
4750
- inject: () => ({ ctx, scope: settingsScope }),
4750
+ inject: () => ({ ctx, scope: configForms }),
4751
4751
  },
4752
4752
  GoalSettingsCard,
4753
4753
  );
@@ -4758,7 +4758,7 @@ window.__ModuleLoader__.load({
4758
4758
  name: 'settings.plugin.item',
4759
4759
  key: NS,
4760
4760
  locale: NS,
4761
- inject: () => ({ ctx, scope: settingsScope }),
4761
+ inject: () => ({ ctx, scope: configForms }),
4762
4762
  },
4763
4763
  GoalSettingsCard,
4764
4764
  );
@@ -4774,9 +4774,9 @@ window.__ModuleLoader__.load({
4774
4774
  ctx.locale?.register?.(NS, LOCALES);
4775
4775
  } catch (err) { /* handled defensively */ }
4776
4776
 
4777
- const settingsScope =
4778
- ctx.settingsScope && typeof ctx.settingsScope.bind === 'function'
4779
- ? ctx.settingsScope.bind({ namespace: NS })
4777
+ const configForms =
4778
+ ctx.configForms && typeof ctx.configForms.get === 'function'
4779
+ ? ctx.configForms.get(NS)
4780
4780
  : null;
4781
4781
 
4782
4782
  const registerSlotSafe = (name, entry, comp) => {
@@ -4813,7 +4813,7 @@ window.__ModuleLoader__.load({
4813
4813
  order: 60,
4814
4814
  label: () => 'Goal',
4815
4815
  locale: NS,
4816
- inject: () => ({ ctx, scope: settingsScope }),
4816
+ inject: () => ({ ctx, scope: configForms }),
4817
4817
  },
4818
4818
  GoalSettingsCard,
4819
4819
  );
@@ -4824,7 +4824,7 @@ window.__ModuleLoader__.load({
4824
4824
  name: 'plugins.row.config',
4825
4825
  key: ROW_CONFIG_KEY,
4826
4826
  locale: NS,
4827
- inject: () => ({ ctx, scope: settingsScope }),
4827
+ inject: () => ({ ctx, scope: configForms }),
4828
4828
  },
4829
4829
  GoalSettingsCard,
4830
4830
  );
@@ -4835,7 +4835,7 @@ window.__ModuleLoader__.load({
4835
4835
  name: 'settings.plugin.item',
4836
4836
  key: NS,
4837
4837
  locale: NS,
4838
- inject: () => ({ ctx, scope: settingsScope }),
4838
+ inject: () => ({ ctx, scope: configForms }),
4839
4839
  },
4840
4840
  GoalSettingsCard,
4841
4841
  );
@@ -225,6 +225,7 @@ export class GoalEngine {
225
225
  title: cleanTitle,
226
226
  description: options.description?.trim() || '',
227
227
  lang: options.lang || detectLanguage(cleanTitle),
228
+ cwd: options.cwd ? String(options.cwd).trim() : null,
228
229
  state: GoalState.RUNNING,
229
230
  startedAt: now,
230
231
  pausedAt: null,
package/lib/routes.js CHANGED
@@ -3,16 +3,82 @@ import path from 'node:path';
3
3
  import { MilestoneStatus, detectLanguage, sessionIdOf } from './goal-engine-constants.js';
4
4
  import { saveGoalArtifact, rollbackToCheckpoint } from './engine-reports.js';
5
5
 
6
- function getSafeCwd(requestedCwd) {
7
- if (!requestedCwd || typeof requestedCwd !== 'string') {
8
- return process.cwd();
6
+ export function isLoopback(value) {
7
+ if (!value) return false;
8
+ const address = String(value).toLowerCase().replace(/^\[|\]$/g, '');
9
+ return Boolean(
10
+ address === 'localhost' ||
11
+ address === 'localhost.' ||
12
+ address === '::1' ||
13
+ address.startsWith('127.') ||
14
+ address.startsWith('::ffff:127.')
15
+ );
16
+ }
17
+
18
+ export function isTrustedCaller(req) {
19
+ if (!req) return false;
20
+ const remote = req.socket?.remoteAddress || req.connection?.remoteAddress || '';
21
+ if (isLoopback(remote)) return true;
22
+
23
+ const secSite = String(req.headers?.['sec-fetch-site'] || '').toLowerCase();
24
+ if (secSite === 'same-origin' || secSite === 'same-site' || secSite === 'none') return true;
25
+ if (secSite === 'cross-site') return false;
26
+
27
+ const host = String(req.headers?.host || '').toLowerCase();
28
+ const origin = String(req.headers?.origin || '').trim().toLowerCase();
29
+ if (origin) {
30
+ try {
31
+ const u = new URL(origin);
32
+ if (u.host === host) return true;
33
+ } catch (err) { /* invalid url */ }
34
+ return false;
35
+ }
36
+
37
+ const referer = String(req.headers?.referer || '').trim().toLowerCase();
38
+ if (referer) {
39
+ try {
40
+ const u = new URL(referer);
41
+ if (u.host === host) return true;
42
+ } catch (err) { /* invalid url */ }
43
+ return false;
9
44
  }
45
+
46
+ // Fallback for tests or local CLI without socket where host header targets loopback
47
+ if (!remote && host) {
48
+ const hostName = host.split(':')[0];
49
+ if (isLoopback(hostName)) {
50
+ return true;
51
+ }
52
+ }
53
+
54
+ return false;
55
+ }
56
+
57
+ export function isPathInsideOrEqual(parent, child) {
58
+ if (!parent || !child) return false;
59
+ const rel = path.relative(path.resolve(parent), path.resolve(child));
60
+ return !rel || (!rel.startsWith('..') && !path.isAbsolute(rel));
61
+ }
62
+
63
+ export function getSafeCwd(requestedCwd, allowedRoots = []) {
64
+ const defaultRoot = allowedRoots[0] || process.cwd();
65
+ if (!requestedCwd || typeof requestedCwd !== 'string' || !requestedCwd.trim()) {
66
+ return defaultRoot;
67
+ }
68
+
10
69
  try {
11
70
  const resolved = path.resolve(requestedCwd.trim());
12
71
  const st = fs.statSync(resolved);
13
72
  if (!st.isDirectory()) {
14
73
  return null;
15
74
  }
75
+
76
+ const roots = allowedRoots.length > 0 ? allowedRoots : [process.cwd()];
77
+ const isAllowed = roots.some((root) => isPathInsideOrEqual(root, resolved));
78
+ if (!isAllowed) {
79
+ return null;
80
+ }
81
+
16
82
  return resolved;
17
83
  } catch (err) {
18
84
  return null;
@@ -144,8 +210,8 @@ export function registerRoutes(ctx, {
144
210
  return res.end(JSON.stringify({ error: 'Forbidden: cross-site requests are rejected' }));
145
211
  }
146
212
 
147
- const origin = req.headers.origin;
148
- const host = req.headers.host;
213
+ const origin = req.headers?.origin;
214
+ const host = req.headers?.host;
149
215
  if (origin && host) {
150
216
  try {
151
217
  const originHost = new URL(origin).host;
@@ -159,6 +225,11 @@ export function registerRoutes(ctx, {
159
225
  }
160
226
  }
161
227
 
228
+ if (!isTrustedCaller(req)) {
229
+ res.statusCode = 403;
230
+ return res.end(JSON.stringify({ error: 'Forbidden: untrusted caller origin' }));
231
+ }
232
+
162
233
  let body = '';
163
234
  let bodySize = 0;
164
235
  const MAX_PAYLOAD_BYTES = 256 * 1024;
@@ -196,6 +267,7 @@ export function registerRoutes(ctx, {
196
267
  description: typeof description === 'string' ? description.trim() : '',
197
268
  maxIterations: conf.maxIterations || 25,
198
269
  lang: detectedLang,
270
+ cwd: data.cwd,
199
271
  }, sid);
200
272
 
201
273
  const startPrompt = detectedLang === 'zh'
@@ -309,8 +381,11 @@ export function registerRoutes(ctx, {
309
381
  res.statusCode = 400;
310
382
  return res.end(JSON.stringify({ error: 'commit hash is required for rollback' }));
311
383
  }
312
- const safeCwd = getSafeCwd(data.cwd);
313
- if (data.cwd && !safeCwd) {
384
+ const sessionGoal = engine.getGoal(sid);
385
+ const allowedRoots = [process.cwd()];
386
+ if (sessionGoal?.cwd) allowedRoots.push(sessionGoal.cwd);
387
+ const safeCwd = getSafeCwd(data.cwd, allowedRoots);
388
+ if (!safeCwd) {
314
389
  res.statusCode = 400;
315
390
  return res.end(JSON.stringify({ error: 'Invalid or non-existent working directory' }));
316
391
  }
@@ -329,8 +404,11 @@ export function registerRoutes(ctx, {
329
404
  res.statusCode = 404;
330
405
  return res.end(JSON.stringify({ error: 'No active goal to save artifact for' }));
331
406
  }
332
- const safeCwd = getSafeCwd(data.cwd);
333
- if (data.cwd && !safeCwd) {
407
+ const sessionGoal = engine.getGoal(sid);
408
+ const allowedRoots = [process.cwd()];
409
+ if (sessionGoal?.cwd) allowedRoots.push(sessionGoal.cwd);
410
+ const safeCwd = getSafeCwd(data.cwd, allowedRoots);
411
+ if (!safeCwd) {
334
412
  res.statusCode = 400;
335
413
  return res.end(JSON.stringify({ error: 'Invalid or non-existent working directory' }));
336
414
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-goal",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Autonomous Goal Execution & Multi-Turn Task Tracking Engine with Sticky Header for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -45,7 +45,11 @@
45
45
  },
46
46
  "client": {
47
47
  "platform": "web",
48
- "inject": []
48
+ "inject": [
49
+ "@deepseek-ai/dsh-client-ui-slots",
50
+ "@deepseek-ai/dsh-client-locale",
51
+ "@deepseek-ai/dsh-client-ui-settings"
52
+ ]
49
53
  }
50
54
  },
51
55
  "peerDependencies": {