@runa-ai/runa-cli 0.5.38 → 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.
- package/dist/commands/db/preflight/actors.d.ts.map +1 -1
- package/dist/commands/inject-test-attrs/manifest-generator.d.ts +6 -0
- package/dist/commands/inject-test-attrs/manifest-generator.d.ts.map +1 -1
- package/dist/index.js +40 -15
- package/dist/utils/path-security.d.ts +8 -6
- package/dist/utils/path-security.d.ts.map +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actors.d.ts","sourceRoot":"","sources":["../../../../src/commands/db/preflight/actors.ts"],"names":[],"mappings":"
|
|
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
|
|
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"}
|
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.
|
|
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:
|
|
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 (
|
|
7227
|
+
if (existsSync50(join22(current, "turbo.json"))) {
|
|
7228
7228
|
return current;
|
|
7229
7229
|
}
|
|
7230
7230
|
const pkgPath = join22(current, "package.json");
|
|
7231
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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}`);
|
|
@@ -30555,6 +30575,11 @@ async function generateManifestFiles(manifestDir, repoRoot, verbose, resolveSche
|
|
|
30555
30575
|
const definitionMap = buildDefinitionMap(machineDefinitions);
|
|
30556
30576
|
const enhancedMachines = buildEnhancedMachines(e2eManifest, definitionMap, machineDefinitions);
|
|
30557
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
|
+
}
|
|
30558
30583
|
ensureDirectoryExists(absoluteManifestDir);
|
|
30559
30584
|
const generatedDir = path10.join(absoluteManifestDir, "generated");
|
|
30560
30585
|
ensureDirectoryExists(generatedDir);
|
|
@@ -33649,15 +33674,15 @@ function printActionsNeeded(logger15, actions) {
|
|
|
33649
33674
|
);
|
|
33650
33675
|
}
|
|
33651
33676
|
function findRepoRoot3(startDir) {
|
|
33652
|
-
const { existsSync:
|
|
33677
|
+
const { existsSync: existsSync50, readFileSync: readFileSync27 } = __require("fs");
|
|
33653
33678
|
const { join: join22, dirname: dirname5 } = __require("path");
|
|
33654
33679
|
let current = startDir;
|
|
33655
33680
|
while (current !== dirname5(current)) {
|
|
33656
|
-
if (
|
|
33681
|
+
if (existsSync50(join22(current, "turbo.json"))) {
|
|
33657
33682
|
return current;
|
|
33658
33683
|
}
|
|
33659
33684
|
const pkgPath = join22(current, "package.json");
|
|
33660
|
-
if (
|
|
33685
|
+
if (existsSync50(pkgPath)) {
|
|
33661
33686
|
try {
|
|
33662
33687
|
const pkg = JSON.parse(readFileSync27(pkgPath, "utf-8"));
|
|
33663
33688
|
if (pkg.workspaces) {
|
|
@@ -33725,10 +33750,10 @@ function generateReportOutput(output3, isJsonMode) {
|
|
|
33725
33750
|
};
|
|
33726
33751
|
}
|
|
33727
33752
|
function validateRunaRepo(repoRoot) {
|
|
33728
|
-
const { existsSync:
|
|
33753
|
+
const { existsSync: existsSync50 } = __require("fs");
|
|
33729
33754
|
const { join: join22 } = __require("path");
|
|
33730
33755
|
const templateDir = join22(repoRoot, "packages/runa-templates/templates");
|
|
33731
|
-
if (!
|
|
33756
|
+
if (!existsSync50(templateDir)) {
|
|
33732
33757
|
throw new CLIError("template-check is a runa-repo only command", "NOT_RUNA_REPO", [
|
|
33733
33758
|
"This command compares runa-repo with pj-repo templates",
|
|
33734
33759
|
"It should only be run in the runa repository",
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* Pattern:
|
|
8
8
|
* 1. Normalize paths to remove .. sequences
|
|
9
9
|
* 2. Resolve to absolute paths
|
|
10
|
-
* 3.
|
|
11
|
-
* 4.
|
|
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
|
|
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.
|
|
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": "
|
|
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.
|
|
56
|
+
"@runa-ai/runa": "0.5.39",
|
|
56
57
|
"@runa-ai/runa-xstate-test-plugin": "0.5.35"
|
|
57
58
|
},
|
|
58
59
|
"engines": {
|