@runa-ai/runa-cli 0.5.37 → 0.5.39

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.
@@ -1 +1 @@
1
- {"version":3,"file":"actors.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/preflight/actors.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAe,gBAAgB,EAAE,iBAAiB,EAAa,MAAM,eAAe,CAAC;AA2GjG;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,gBAAgB,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,iBAAiB,CAAC,CAuD5B"}
1
+ {"version":3,"file":"actors.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/preflight/actors.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAe,gBAAgB,EAAE,iBAAiB,EAAa,MAAM,eAAe,CAAC;AA4HjG;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,gBAAgB,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,iBAAiB,CAAC,CAuD5B"}
@@ -3,6 +3,12 @@
3
3
  *
4
4
  * Purpose: Generate E2E selector manifest and API contracts
5
5
  * Pattern: Build unified manifest from XState machines and Hono routes
6
+ *
7
+ * Security:
8
+ * - Validates manifest output directory is within project root
9
+ * - Prevents path traversal attacks via --manifest-dir argument
10
+ *
11
+ * @see Issue #... - Manifest file path traversal vulnerability
6
12
  */
7
13
  import type { MachineWithoutE2EMeta } from './contract.js';
8
14
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-generator.d.ts","sourceRoot":"","sources":["../../../src/commands/inject-test-attrs/manifest-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAkY3D;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,cAAc,GAAE,OAAe,GAC9B,OAAO,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;CACjD,CAAC,CAyHD"}
1
+ {"version":3,"file":"manifest-generator.d.ts","sourceRoot":"","sources":["../../../src/commands/inject-test-attrs/manifest-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAqY3D;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,cAAc,GAAE,OAAe,GAC9B,OAAO,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;CACjD,CAAC,CAkID"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-layer.d.ts","sourceRoot":"","sources":["../../../../src/commands/test/commands/test-layer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoKpC,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AAmDvD,eAAO,MAAM,gBAAgB,SAI5B,CAAC"}
1
+ {"version":3,"file":"test-layer.d.ts","sourceRoot":"","sources":["../../../../src/commands/test/commands/test-layer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6KpC,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AACvD,eAAO,MAAM,iBAAiB,SAAwB,CAAC;AAmDvD,eAAO,MAAM,gBAAgB,SAI5B,CAAC"}
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * Sync strategy: Keep this in sync with packages/runa-templates/package.json version.
22
22
  */
23
- export declare const COMPATIBLE_TEMPLATES_VERSION = "0.5.37";
23
+ export declare const COMPATIBLE_TEMPLATES_VERSION = "0.5.38";
24
24
  /**
25
25
  * Templates package name on GitHub Packages.
26
26
  * Published to npm.pkg.github.com (requires NODE_AUTH_TOKEN).
package/dist/index.js CHANGED
@@ -925,7 +925,7 @@ var CLI_VERSION, HAS_ADMIN_COMMAND;
925
925
  var init_version = __esm({
926
926
  "src/version.ts"() {
927
927
  init_esm_shims();
928
- CLI_VERSION = "0.5.37";
928
+ CLI_VERSION = "0.5.39";
929
929
  HAS_ADMIN_COMMAND = false;
930
930
  }
931
931
  });
@@ -7220,15 +7220,15 @@ function printSummary(logger15, output3) {
7220
7220
  }
7221
7221
  }
7222
7222
  function findRepoRoot(startDir) {
7223
- const { existsSync: existsSync49, readFileSync: readFileSync27 } = __require("fs");
7223
+ const { existsSync: existsSync50, readFileSync: readFileSync27 } = __require("fs");
7224
7224
  const { join: join22, dirname: dirname5 } = __require("path");
7225
7225
  let current = startDir;
7226
7226
  while (current !== dirname5(current)) {
7227
- if (existsSync49(join22(current, "turbo.json"))) {
7227
+ if (existsSync50(join22(current, "turbo.json"))) {
7228
7228
  return current;
7229
7229
  }
7230
7230
  const pkgPath = join22(current, "package.json");
7231
- if (existsSync49(pkgPath)) {
7231
+ if (existsSync50(pkgPath)) {
7232
7232
  try {
7233
7233
  const pkg = JSON.parse(readFileSync27(pkgPath, "utf-8"));
7234
7234
  if (pkg.workspaces) {
@@ -8351,6 +8351,16 @@ init_esm_shims();
8351
8351
 
8352
8352
  // src/utils/path-security.ts
8353
8353
  init_esm_shims();
8354
+ function safeRealpath(targetPath) {
8355
+ try {
8356
+ if (existsSync(targetPath)) {
8357
+ return realpathSync(targetPath);
8358
+ }
8359
+ return targetPath;
8360
+ } catch {
8361
+ return targetPath;
8362
+ }
8363
+ }
8354
8364
  var SHELL_METACHARACTERS = /[|;`$&<>]/;
8355
8365
  function isControlChar(charCode) {
8356
8366
  return charCode <= 31 || charCode === 127;
@@ -8388,7 +8398,9 @@ function validateSafePath(userPath, baseDir) {
8388
8398
  }
8389
8399
  const absoluteBase = resolve(baseDir);
8390
8400
  const absolutePath = resolve(baseDir, userPath);
8391
- const relativePath = relative(absoluteBase, absolutePath);
8401
+ const realBase = safeRealpath(absoluteBase);
8402
+ const realPath = safeRealpath(absolutePath);
8403
+ const relativePath = relative(realBase, realPath);
8392
8404
  if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
8393
8405
  return false;
8394
8406
  }
@@ -8426,7 +8438,9 @@ function validateEnvSuffix(suffix) {
8426
8438
  function isPathContained(basePath, targetPath) {
8427
8439
  const normalizedBase = resolve(basePath);
8428
8440
  const normalizedTarget = resolve(targetPath);
8429
- return normalizedTarget === normalizedBase || normalizedTarget.startsWith(normalizedBase + sep);
8441
+ const realBase = safeRealpath(normalizedBase);
8442
+ const realTarget = safeRealpath(normalizedTarget);
8443
+ return realTarget === realBase || realTarget.startsWith(realBase + sep);
8430
8444
  }
8431
8445
 
8432
8446
  // src/config/env-files.ts
@@ -24590,9 +24604,12 @@ function getTablesWithTimestamps(dbUrl, schemas) {
24590
24604
  AND c.relkind = 'r'
24591
24605
  ORDER BY n.nspname, c.relname;
24592
24606
  `;
24593
- const result = spawnSync("psql", [dbUrl, "-t", "-A", "-c", query], {
24607
+ const conn = parsePostgresUrl(dbUrl);
24608
+ const args = buildPsqlArgs(conn, { onErrorStop: false });
24609
+ const result = spawnSync("psql", [...args, "-t", "-A", "-c", query], {
24594
24610
  encoding: "utf-8",
24595
- stdio: ["pipe", "pipe", "pipe"]
24611
+ stdio: ["pipe", "pipe", "pipe"],
24612
+ env: buildPsqlEnv(conn)
24596
24613
  });
24597
24614
  if (result.status !== 0) {
24598
24615
  throw new Error(`Failed to query tables: ${result.stderr}`);
@@ -24601,9 +24618,12 @@ function getTablesWithTimestamps(dbUrl, schemas) {
24601
24618
  }
24602
24619
  function countTimestampViolations(dbUrl, schema, table) {
24603
24620
  const query = `SELECT COUNT(*) FROM "${schema}"."${table}" WHERE updated_at < created_at`;
24604
- const result = spawnSync("psql", [dbUrl, "-t", "-A", "-c", query], {
24621
+ const conn = parsePostgresUrl(dbUrl);
24622
+ const args = buildPsqlArgs(conn, { onErrorStop: false });
24623
+ const result = spawnSync("psql", [...args, "-t", "-A", "-c", query], {
24605
24624
  encoding: "utf-8",
24606
- stdio: ["pipe", "pipe", "pipe"]
24625
+ stdio: ["pipe", "pipe", "pipe"],
24626
+ env: buildPsqlEnv(conn)
24607
24627
  });
24608
24628
  if (result.status !== 0) {
24609
24629
  throw new Error(`Failed to check ${schema}.${table}: ${result.stderr}`);
@@ -29386,7 +29406,7 @@ init_esm_shims();
29386
29406
 
29387
29407
  // src/constants/versions.ts
29388
29408
  init_esm_shims();
29389
- var COMPATIBLE_TEMPLATES_VERSION = "0.5.37";
29409
+ var COMPATIBLE_TEMPLATES_VERSION = "0.5.38";
29390
29410
  var TEMPLATES_PACKAGE_NAME = "@r06-dev/runa-templates";
29391
29411
  var GITHUB_PACKAGES_REGISTRY = "https://npm.pkg.github.com";
29392
29412
 
@@ -30510,7 +30530,10 @@ async function analyzeHonoRoutes(repoRoot, verbose, resolveSchemas = false) {
30510
30530
  sourceFile: route.fileName,
30511
30531
  resourceName: route.resourceName,
30512
30532
  hasParams: route.hasParams,
30513
- params: route.params
30533
+ params: route.params,
30534
+ // v8+: Include export info for correct import generation
30535
+ exportType: route.exportType,
30536
+ exportName: route.exportName
30514
30537
  };
30515
30538
  if (resolveSchemas && "inputSchema" in route && route.inputSchema) {
30516
30539
  contract.inputSchema = route.inputSchema;
@@ -30552,6 +30575,11 @@ async function generateManifestFiles(manifestDir, repoRoot, verbose, resolveSche
30552
30575
  const definitionMap = buildDefinitionMap(machineDefinitions);
30553
30576
  const enhancedMachines = buildEnhancedMachines(e2eManifest, definitionMap, machineDefinitions);
30554
30577
  const absoluteManifestDir = path10.isAbsolute(manifestDir) ? manifestDir : path10.join(repoRoot, manifestDir);
30578
+ if (!isPathContained(repoRoot, absoluteManifestDir)) {
30579
+ throw new Error(
30580
+ `Security error: Manifest directory '${manifestDir}' would escape the project root. The --manifest-dir must be a relative path within the project directory.`
30581
+ );
30582
+ }
30555
30583
  ensureDirectoryExists(absoluteManifestDir);
30556
30584
  const generatedDir = path10.join(absoluteManifestDir, "generated");
30557
30585
  ensureDirectoryExists(generatedDir);
@@ -33646,15 +33674,15 @@ function printActionsNeeded(logger15, actions) {
33646
33674
  );
33647
33675
  }
33648
33676
  function findRepoRoot3(startDir) {
33649
- const { existsSync: existsSync49, readFileSync: readFileSync27 } = __require("fs");
33677
+ const { existsSync: existsSync50, readFileSync: readFileSync27 } = __require("fs");
33650
33678
  const { join: join22, dirname: dirname5 } = __require("path");
33651
33679
  let current = startDir;
33652
33680
  while (current !== dirname5(current)) {
33653
- if (existsSync49(join22(current, "turbo.json"))) {
33681
+ if (existsSync50(join22(current, "turbo.json"))) {
33654
33682
  return current;
33655
33683
  }
33656
33684
  const pkgPath = join22(current, "package.json");
33657
- if (existsSync49(pkgPath)) {
33685
+ if (existsSync50(pkgPath)) {
33658
33686
  try {
33659
33687
  const pkg = JSON.parse(readFileSync27(pkgPath, "utf-8"));
33660
33688
  if (pkg.workspaces) {
@@ -33722,10 +33750,10 @@ function generateReportOutput(output3, isJsonMode) {
33722
33750
  };
33723
33751
  }
33724
33752
  function validateRunaRepo(repoRoot) {
33725
- const { existsSync: existsSync49 } = __require("fs");
33753
+ const { existsSync: existsSync50 } = __require("fs");
33726
33754
  const { join: join22 } = __require("path");
33727
33755
  const templateDir = join22(repoRoot, "packages/runa-templates/templates");
33728
- if (!existsSync49(templateDir)) {
33756
+ if (!existsSync50(templateDir)) {
33729
33757
  throw new CLIError("template-check is a runa-repo only command", "NOT_RUNA_REPO", [
33730
33758
  "This command compares runa-repo with pj-repo templates",
33731
33759
  "It should only be run in the runa repository",
@@ -34132,6 +34160,7 @@ async function runSingleLayer(params) {
34132
34160
  filter: params.options.filter,
34133
34161
  reviewSnapshots: params.options.reviewSnapshots,
34134
34162
  forceRegenerate: params.options.force,
34163
+ skipGeneration: params.options.skipGeneration,
34135
34164
  invokedAs: `runa test:layer${params.layer}`
34136
34165
  });
34137
34166
  emitJsonSuccess(params.cmd, TestRunOutputSchema, output3);
@@ -34193,6 +34222,9 @@ function createLayerCommand(layer) {
34193
34222
  ).action(async (options) => {
34194
34223
  await runSingleLayer({ cmd, layer, options });
34195
34224
  });
34225
+ if (layer === 3) {
34226
+ cmd.option("--skip-generation", "Skip auto-generation of API tests (run manual tests only)");
34227
+ }
34196
34228
  if (layer >= 4) {
34197
34229
  cmd.option("--generate", "Generate tests from XState before running (Layer 4)").option("--advanced", "Generate advanced tests (requires --generate)").option("--auto", "Zero-config auto-generation (implementation-driven)").option("--update-snapshots", "Update visual baselines (Layer 4)").option("--filter <pattern>", 'Filter tests by pattern (e.g., "checkout-*")').option("--review-snapshots", "Interactive review of changed visual snapshots (Layer 4)").option("--force", "Force regenerate all tests, bypassing cache");
34198
34230
  }
@@ -7,11 +7,13 @@
7
7
  * Pattern:
8
8
  * 1. Normalize paths to remove .. sequences
9
9
  * 2. Resolve to absolute paths
10
- * 3. Verify resolved path is within allowed base directory
11
- * 4. Reject paths containing dangerous characters
10
+ * 3. Resolve symlinks to prevent bypass attacks
11
+ * 4. Verify resolved path is within allowed base directory
12
+ * 5. Reject paths containing dangerous characters
12
13
  *
13
14
  * @see Issue #453 - Path traversal in seed management file operations
14
15
  * @see Issue #462 - Path traversal in config and environment file loading
16
+ * @see Issue #606 - Symlink bypass in path validation
15
17
  */
16
18
  /**
17
19
  * Validate that a path doesn't contain dangerous characters.
@@ -34,7 +36,7 @@ export declare function containsPathTraversal(userPath: string): boolean;
34
36
  * Security checks:
35
37
  * 1. Path doesn't contain dangerous characters
36
38
  * 2. Path doesn't contain path traversal sequences (../)
37
- * 3. Resolved path is within the base directory
39
+ * 3. Resolved path (after following symlinks) is within the base directory
38
40
  *
39
41
  * @param userPath - The user-provided path (relative)
40
42
  * @param baseDir - The base directory the path should be within
@@ -77,7 +79,7 @@ export declare const MAX_DIRECTORY_TRAVERSAL_DEPTH = 10;
77
79
  */
78
80
  export declare function validateEnvSuffix(suffix: string): void;
79
81
  /**
80
- * SECURITY (Issue #462): Build a safe environment file path.
82
+ * SECURITY (Issue #462, #606): Build a safe environment file path.
81
83
  * Validates the environment name before constructing the path.
82
84
  *
83
85
  * @param projectRoot - Project root directory
@@ -87,8 +89,8 @@ export declare function validateEnvSuffix(suffix: string): void;
87
89
  */
88
90
  export declare function buildSafeEnvFilePath(projectRoot: string, envName: string, local?: boolean): string;
89
91
  /**
90
- * SECURITY (Issue #462): Verify a path is contained within a base directory.
91
- * Prevents escaping the intended directory via path manipulation.
92
+ * SECURITY (Issue #462, #606): Verify a path is contained within a base directory.
93
+ * Prevents escaping the intended directory via path manipulation or symlink attacks.
92
94
  *
93
95
  * @param basePath - The expected parent directory
94
96
  * @param targetPath - The path to verify
@@ -1 +1 @@
1
- {"version":3,"file":"path-security.d.ts","sourceRoot":"","sources":["../../src/utils/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiBH;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAc7D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAW/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CA6B3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACvC,MAAM,EAAE,CAQV;AAMD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAQhD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiBtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,MAAM,CAahG;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAI7E"}
1
+ {"version":3,"file":"path-security.d.ts","sourceRoot":"","sources":["../../src/utils/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAyCH;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAc7D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAW/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAmC3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CASzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACvC,MAAM,EAAE,CAQV;AAMD;;;GAGG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAQhD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiBtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,MAAM,CAehG;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAS7E"}
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@runa-ai/runa-cli",
3
- "version": "0.5.37",
3
+ "version": "0.5.39",
4
4
  "private": false,
5
5
  "description": "AI-powered DevOps CLI",
6
6
  "type": "module",
7
+ "license": "MIT",
7
8
  "publishConfig": {
8
9
  "access": "public"
9
10
  },
@@ -29,7 +30,7 @@
29
30
  "@types/node": "22.19.3",
30
31
  "boxen": "7.1.1",
31
32
  "chalk": "5.6.2",
32
- "chokidar": "3.6.0",
33
+ "chokidar": "5.0.0",
33
34
  "commander": "12.1.0",
34
35
  "dotenv": "17.2.3",
35
36
  "dotenv-expand": "12.0.3",
@@ -52,7 +53,7 @@
52
53
  "typescript": "5.9.3",
53
54
  "xstate": "5.25.0",
54
55
  "zod": "4.3.5",
55
- "@runa-ai/runa": "0.5.33",
56
+ "@runa-ai/runa": "0.5.39",
56
57
  "@runa-ai/runa-xstate-test-plugin": "0.5.35"
57
58
  },
58
59
  "engines": {