fix: address review feedback on !include and folder workflow

- Fix nested !include: pass customTags recursively, scoped to included file's dir
- Add path traversal guard: !include paths must resolve within base directory
- Fix discoverProjectWorkflows: scan both .workflow/ and .workflows/ (consistent with findWorkflowInDir)
- Add tests: path traversal blocking, nested !include, absolute path rejection
This commit is contained in:
2026-05-31 04:26:54 +00:00
parent 88c251fc14
commit da1678ffef
3 changed files with 62 additions and 9 deletions
+15 -3
View File
@@ -1,23 +1,35 @@
import { readFileSync } from "node:fs";
import { extname, resolve } from "node:path";
import { dirname, extname, resolve } from "node:path";
import { parse as parseYaml } from "yaml";
/**
* Create a YAML customTags entry for !include that resolves file paths
* relative to the given base directory.
*
* Security: resolved paths must stay within baseDir (path traversal prevention).
* Nested !include in .yaml/.yml files is supported (customTags passed recursively).
*/
export function createIncludeTag(baseDir: string) {
const resolvedBase = resolve(baseDir);
return {
tag: "!include",
resolve(str: string) {
const filePath = resolve(baseDir, str);
const filePath = resolve(resolvedBase, str);
// Path traversal guard: resolved path must be inside baseDir
if (!filePath.startsWith(resolvedBase + "/") && filePath !== resolvedBase) {
throw new Error(
`!include path traversal blocked: "${str}" resolves outside base directory`,
);
}
const content = readFileSync(filePath, "utf8");
const ext = extname(filePath).toLowerCase();
if (ext === ".json") {
return JSON.parse(content);
}
if (ext === ".yaml" || ext === ".yml") {
return parseYaml(content);
// Pass customTags recursively so nested !include works,
// scoped to the included file's directory
return parseYaml(content, { customTags: [createIncludeTag(dirname(filePath))] });
}
return content;
},