fix(prometheus-md-only): allow nested project paths with .sisyphus directory

Use regex /\.sisyphus[/\\]/i instead of checking first path segment. This fixes Windows paths where ctx.directory is parent of the actual project (e.g., project\.sisyphus\drafts\...).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-01-11 15:27:51 +09:00
parent 1132be370c
commit 49b0b5e085
2 changed files with 63 additions and 8 deletions

View File

@@ -373,8 +373,8 @@ describe("prometheus-md-only", () => {
).rejects.toThrow("can only write/edit .md files inside .sisyphus/")
})
test("should block nested .sisyphus directories", async () => {
// #given
test("should allow nested .sisyphus directories (ctx.directory may be parent)", async () => {
// #given - when ctx.directory is parent of actual project, path includes project name
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
const input = {
tool: "Write",
@@ -385,10 +385,10 @@ describe("prometheus-md-only", () => {
args: { filePath: "src/.sisyphus/plans/x.md" },
}
// #when / #then
// #when / #then - should allow because .sisyphus is in path
await expect(
hook["tool.execute.before"](input, output)
).rejects.toThrow("can only write/edit .md files inside .sisyphus/")
).resolves.toBeUndefined()
})
test("should block path traversal attempts", async () => {
@@ -426,5 +426,60 @@ describe("prometheus-md-only", () => {
hook["tool.execute.before"](input, output)
).resolves.toBeUndefined()
})
test("should allow nested project path with .sisyphus (Windows real-world case)", async () => {
// #given - simulates when ctx.directory is parent of actual project
// User reported: xauusd-dxy-plan\.sisyphus\drafts\supabase-email-templates.md
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
const input = {
tool: "Write",
sessionID: TEST_SESSION_ID,
callID: "call-1",
}
const output = {
args: { filePath: "xauusd-dxy-plan\\.sisyphus\\drafts\\supabase-email-templates.md" },
}
// #when / #then
await expect(
hook["tool.execute.before"](input, output)
).resolves.toBeUndefined()
})
test("should allow nested project path with mixed separators", async () => {
// #given
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
const input = {
tool: "Write",
sessionID: TEST_SESSION_ID,
callID: "call-1",
}
const output = {
args: { filePath: "my-project/.sisyphus\\plans/task.md" },
}
// #when / #then
await expect(
hook["tool.execute.before"](input, output)
).resolves.toBeUndefined()
})
test("should block nested project path without .sisyphus", async () => {
// #given
const hook = createPrometheusMdOnlyHook(createMockPluginInput())
const input = {
tool: "Write",
sessionID: TEST_SESSION_ID,
callID: "call-1",
}
const output = {
args: { filePath: "my-project\\src\\code.ts" },
}
// #when / #then
await expect(
hook["tool.execute.before"](input, output)
).rejects.toThrow("can only write/edit .md files")
})
})
})

View File

@@ -14,6 +14,7 @@ export * from "./constants"
* - Mixed separators (e.g., .sisyphus\\plans/x.md)
* - Case-insensitive directory/extension matching
* - Workspace confinement (blocks paths outside root or via traversal)
* - Nested project paths (e.g., parent/.sisyphus/... when ctx.directory is parent)
*/
function isAllowedFile(filePath: string, workspaceRoot: string): boolean {
// 1. Resolve to absolute path
@@ -27,10 +28,9 @@ function isAllowedFile(filePath: string, workspaceRoot: string): boolean {
return false
}
// 4. Split by both separators and check first segment matches ALLOWED_PATH_PREFIX (case-insensitive)
// Guard: if rel is empty (filePath === workspaceRoot), segments[0] would be "" — reject
const segments = rel.split(/[/\\]/)
if (!segments[0] || segments[0].toLowerCase() !== ALLOWED_PATH_PREFIX.toLowerCase()) {
// 4. Check if .sisyphus/ or .sisyphus\ exists anywhere in the path (case-insensitive)
// This handles both direct paths (.sisyphus/x.md) and nested paths (project/.sisyphus/x.md)
if (!/\.sisyphus[/\\]/i.test(rel)) {
return false
}