From 544b8b9f32dd3ba3cc4e90d90ef427d60b8ae1f5 Mon Sep 17 00:00:00 2001 From: Proactive Runtime Bot Date: Mon, 1 Jun 2026 09:19:58 +0200 Subject: [PATCH] fix(mountsync): preserve scoped write path case --- internal/mountsync/syncer.go | 10 +++++----- internal/mountsync/syncer_test.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/internal/mountsync/syncer.go b/internal/mountsync/syncer.go index b6d25769..ef3e4af7 100644 --- a/internal/mountsync/syncer.go +++ b/internal/mountsync/syncer.go @@ -4322,12 +4322,12 @@ func (s *Syncer) canWritePath(filePath string) bool { } func scopeGrantsWrite(scope, filePath string) bool { - scope = strings.ToLower(strings.TrimSpace(scope)) + scope = strings.TrimSpace(scope) if scope == "" { return false } // Short-form scope without plane prefix. - if scope == "fs:write" || scope == "fs:manage" { + if strings.EqualFold(scope, "fs:write") || strings.EqualFold(scope, "fs:manage") { return true } @@ -4336,9 +4336,9 @@ func scopeGrantsWrite(scope, filePath string) bool { return false } - plane := segments[0] - res := segments[1] - act := segments[2] + plane := strings.ToLower(strings.TrimSpace(segments[0])) + res := strings.ToLower(strings.TrimSpace(segments[1])) + act := strings.ToLower(strings.TrimSpace(segments[2])) // Plane must be "relayfile" or wildcard. if plane != "relayfile" && plane != "*" { diff --git a/internal/mountsync/syncer_test.go b/internal/mountsync/syncer_test.go index d5191c76..12f2dfbc 100644 --- a/internal/mountsync/syncer_test.go +++ b/internal/mountsync/syncer_test.go @@ -3198,6 +3198,21 @@ func TestCanWritePathWithRelayauthScopes(t *testing.T) { } } +func TestScopeGrantsWritePreservesPathCase(t *testing.T) { + if !scopeGrantsWrite("relayfile:fs:write:/README.md/*", "/README.md") { + t.Fatalf("write scope over /README.md must grant /README.md (case preserved)") + } + if !scopeGrantsWrite("relayfile:fs:write:/packages/web/lib/MyComponent/*", "/packages/web/lib/MyComponent/index.ts") { + t.Fatalf("write scope over /packages/web/lib/MyComponent must grant files under it") + } + if !scopeGrantsWrite("RELAYFILE:FS:WRITE:/Foo/*", "/Foo/bar.ts") { + t.Fatalf("plane/resource/action must stay case-insensitive") + } + if scopeGrantsWrite("relayfile:fs:write:/Foo/*", "/foo/bar.ts") { + t.Fatalf("case-mismatched path must NOT be granted (paths are case-sensitive)") + } +} + func TestCanReadPathWithPerFileScopes(t *testing.T) { scopes := map[string]struct{}{ "relayfile:fs:read:/src/app.ts": {},