@wrongstack/sdd 0.309.0 → 0.309.1

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/dist/index.js CHANGED
@@ -935,8 +935,26 @@ var TaskGraphStore = class {
935
935
  async deleteGraph(id) {
936
936
  await this.delete(id);
937
937
  }
938
+ /**
939
+ * Resolve a graph id to its file, refusing anything that escapes `baseDir`.
940
+ *
941
+ * `id` arrives straight from a WebSocket payload (`specs.taskStatus` casts it
942
+ * with no validation), and this used to be a bare `path.join` — so
943
+ * `../../../Users/me/secret` resolved outside the store and `load()` returned
944
+ * its contents to the caller. Mirrors the containment `kanban/storage.ts`
945
+ * already applies to board ids.
946
+ */
938
947
  filePath(id) {
939
- return path2.join(this.baseDir, `${id}.json`);
948
+ if (typeof id !== "string" || id.length === 0 || id.length > 200 || /[\0]/.test(id)) {
949
+ throw new Error(`Invalid task-graph id: ${JSON.stringify(id)}`);
950
+ }
951
+ const dir = path2.resolve(this.baseDir);
952
+ const resolved = path2.resolve(dir, `${id}.json`);
953
+ const rel = path2.relative(dir, resolved);
954
+ if (rel.startsWith("..") || path2.isAbsolute(rel) || rel.includes(path2.sep)) {
955
+ throw new Error(`Invalid task-graph id: ${JSON.stringify(id)}`);
956
+ }
957
+ return resolved;
940
958
  }
941
959
  async readIndex() {
942
960
  try {
@@ -5479,7 +5497,7 @@ function makeCommandVerifier(options = {}) {
5479
5497
  return { ok: false, reason: `verification command is malformed: ${rawCommand}` };
5480
5498
  }
5481
5499
  const [executable, ...args] = argv;
5482
- return await new Promise((resolve) => {
5500
+ return await new Promise((resolve2) => {
5483
5501
  const child = spawn(executable, args, {
5484
5502
  cwd: info.cwd,
5485
5503
  shell: false,
@@ -5490,18 +5508,18 @@ function makeCommandVerifier(options = {}) {
5490
5508
  const timer = setTimeout(() => {
5491
5509
  timedOut = true;
5492
5510
  child.kill();
5493
- resolve({ ok: false, reason: `verification timed out: ${rawCommand}` });
5511
+ resolve2({ ok: false, reason: `verification timed out: ${rawCommand}` });
5494
5512
  }, timeoutMs);
5495
5513
  child.on("exit", (code) => {
5496
5514
  clearTimeout(timer);
5497
5515
  if (timedOut) return;
5498
- resolve(
5516
+ resolve2(
5499
5517
  code === 0 ? { ok: true } : { ok: false, reason: `verification failed (exit ${code}): ${rawCommand}` }
5500
5518
  );
5501
5519
  });
5502
5520
  child.on("error", (err) => {
5503
5521
  clearTimeout(timer);
5504
- resolve({ ok: false, reason: `verification spawn error: ${String(err)}` });
5522
+ resolve2({ ok: false, reason: `verification spawn error: ${String(err)}` });
5505
5523
  });
5506
5524
  });
5507
5525
  };
@@ -5662,7 +5680,7 @@ async function decomposeNonAtomicTasks(opts) {
5662
5680
 
5663
5681
  // src/conflict-resolver.ts
5664
5682
  import { readFile as readFile6, writeFile } from "node:fs/promises";
5665
- import { isAbsolute, join as join6 } from "node:path";
5683
+ import { isAbsolute as isAbsolute2, join as join6 } from "node:path";
5666
5684
  import { readBundledInstructionText as readBundledInstructionText2, renderInstructionTemplate as renderInstructionTemplate2 } from "@wrongstack/core/utils";
5667
5685
  var defaultFileIO = {
5668
5686
  read: (path6) => readFile6(path6, "utf8"),
@@ -5711,7 +5729,7 @@ function makePreferSideConflictResolver(side, io = defaultFileIO) {
5711
5729
  return async function conflictResolver(info) {
5712
5730
  if (info.conflictFiles.length === 0) return false;
5713
5731
  for (const rel of info.conflictFiles) {
5714
- const abs = isAbsolute(rel) ? rel : join6(info.cwd, rel);
5732
+ const abs = isAbsolute2(rel) ? rel : join6(info.cwd, rel);
5715
5733
  let content;
5716
5734
  try {
5717
5735
  content = await io.read(abs);
@@ -5745,7 +5763,7 @@ function makeLlmConflictResolver(opts) {
5745
5763
  return async function conflictResolver(info) {
5746
5764
  if (info.conflictFiles.length === 0) return false;
5747
5765
  for (const rel of info.conflictFiles) {
5748
- const abs = isAbsolute(rel) ? rel : join6(info.cwd, rel);
5766
+ const abs = isAbsolute2(rel) ? rel : join6(info.cwd, rel);
5749
5767
  let content;
5750
5768
  try {
5751
5769
  content = await io.read(abs);
@@ -35,6 +35,15 @@ export declare class TaskGraphStore implements TaskStore {
35
35
  updatedAt: number;
36
36
  }>>;
37
37
  deleteGraph(id: string): Promise<void>;
38
+ /**
39
+ * Resolve a graph id to its file, refusing anything that escapes `baseDir`.
40
+ *
41
+ * `id` arrives straight from a WebSocket payload (`specs.taskStatus` casts it
42
+ * with no validation), and this used to be a bare `path.join` — so
43
+ * `../../../Users/me/secret` resolved outside the store and `load()` returned
44
+ * its contents to the caller. Mirrors the containment `kanban/storage.ts`
45
+ * already applies to board ids.
46
+ */
38
47
  private filePath;
39
48
  private readIndex;
40
49
  private updateIndex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/sdd",
3
- "version": "0.309.0",
3
+ "version": "0.309.1",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack Spec-Driven Development engine — standalone package extracted from @wrongstack/core. Task graph generation, tracking, execution, lifecycle management, and AI-driven spec building for SDD workflows.",
6
6
  "repository": {
@@ -27,9 +27,9 @@
27
27
  "!dist/**/*.map"
28
28
  ],
29
29
  "dependencies": {
30
- "@wrongstack/core": "0.309.0",
31
- "@wrongstack/kanban": "0.309.0",
32
- "@wrongstack/requirement-intake": "0.309.0"
30
+ "@wrongstack/requirement-intake": "0.309.1",
31
+ "@wrongstack/core": "0.309.1",
32
+ "@wrongstack/kanban": "0.309.1"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^26.2.0",