Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,31 @@ func TestWriteResource_StillStampsFetchedAt(t *testing.T) {
t.Fatalf("WriteResource must stamp a non-zero FetchedAt")
}
}

func TestUnderscoreComponentsAreValid(t *testing.T) {
// Resource/instance names legitimately use underscores; this must
// round-trip (the first consumer, jtk, imposed no charset limit on
// resource names). Purely permissive widening.
loc := cache.Locator{Root: t.TempDir(), InstanceKey: "host_1"}
if err := cache.WriteResource(loc, "also_ok", "1h", payload{Name: "u", N: 9}); err != nil {
t.Fatalf("WriteResource with underscores: %v", err)
}
got, err := cache.ReadResource[payload](loc, "also_ok")
if err != nil {
t.Fatalf("ReadResource with underscores: %v", err)
}
if got.Data.N != 9 || got.Instance != "host_1" || got.Resource != "also_ok" {
t.Fatalf("underscore round-trip wrong: %+v", got)
}
}

func TestUnderscoreWideningStillRejectsUnsafe(t *testing.T) {
// Regression: widening to allow "_" must not weaken the traversal /
// separator / trailing-dot guards.
root := t.TempDir()
for _, bad := range []string{"a/b", `a\b`, "..", "a..b", "../x", "trailing.", "_leading"} {
if err := cache.WriteResource(cache.Locator{Root: root, InstanceKey: "ok"}, bad, "1h", payload{}); !errors.Is(err, cache.ErrInvalidName) {
t.Fatalf("name %q: err = %v, want ErrInvalidName", bad, err)
}
}
}
12 changes: 8 additions & 4 deletions cache/locator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@ var (

// safeComponent bounds instance keys and resource names to the subset that is
// safe to compose into a filesystem path: letters, digits, dot, hyphen,
// starting alphanumeric. Path separators, whitespace, and control characters
// are rejected rather than trusted (the values are caller-supplied — e.g. a
// hostname derived from config).
var safeComponent = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9.\-]*$`)
// underscore, starting alphanumeric. Path separators, whitespace, and control
// characters are rejected rather than trusted (the values are caller-supplied
// — e.g. a hostname derived from config, or a resource name). Underscore is
// included because resource names legitimately use it (the first consumer,
// jtk, imposed no charset limit on resource names); it is filesystem-safe and
// not a separator, so widening to allow it is purely permissive (no
// previously-valid input becomes invalid).
var safeComponent = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._\-]*$`)

// validComponent also rejects a ".." substring (traversal) and a trailing dot.
// A trailing dot matters cross-OS: Windows (NTFS/FAT) silently strips it, so
Expand Down
Loading