From 0f81f0e532d46765f0fde1e47fedbfe74fc09650 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Fri, 11 Sep 2026 15:10:40 -0600 Subject: [PATCH] Never sync code files to blob storage Adds an explicit codeExtensions denylist alongside the existing mediaExtensions allowlist and consults it first in both the walk and isMediaPath. Source files (.md, .css, .html, .go, .js, .ts, .json, .yaml, .yml, .toml, .mod, .sum, .sh) can never be uploaded, even if a future edit accidentally adds one to the media allowlist. Also adds a unit test that pins this invariant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 41d87383-8698-48c9-a325-6fcc2ff1bbc3 --- syncmedia.go | 38 +++++++++++++++++++++++++++++++++++++- syncmedia_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 syncmedia_test.go diff --git a/syncmedia.go b/syncmedia.go index b96198a8..4462c07c 100644 --- a/syncmedia.go +++ b/syncmedia.go @@ -48,6 +48,10 @@ const ( // mediaExtensions is the set of file extensions treated as "media" (i.e. // non-code) that will be synced to blob storage. Keep in lowercase. +// +// This is an allowlist: files whose extensions are not listed here are never +// uploaded. See codeExtensions for an explicit denylist of source-code +// extensions that must never leak into storage even if this allowlist grows. var mediaExtensions = map[string]bool{ ".png": true, ".jpg": true, @@ -71,6 +75,27 @@ var mediaExtensions = map[string]bool{ ".zip": true, } +// codeExtensions is an explicit denylist of source-code file extensions that +// must NEVER be uploaded to blob storage. Code lives in git; only rendered +// media belongs in the container. This check runs before the mediaExtensions +// allowlist so accidental additions to that allowlist can't slip code through. +// Keep in lowercase. +var codeExtensions = map[string]bool{ + ".md": true, + ".css": true, + ".html": true, + ".go": true, + ".js": true, + ".ts": true, + ".json": true, + ".yaml": true, + ".yml": true, + ".toml": true, + ".mod": true, + ".sum": true, + ".sh": true, +} + // syncMediaPrefixes are the repo-relative directory roots we sync from. var syncMediaPrefixes = []string{ "content/articles", @@ -258,7 +283,15 @@ func watchAndSync( } func isMediaPath(p string) bool { - return mediaExtensions[strings.ToLower(filepath.Ext(p))] + ext := strings.ToLower(filepath.Ext(p)) + if codeExtensions[ext] { + return false + } + return mediaExtensions[ext] +} + +func isCodePath(p string) bool { + return codeExtensions[strings.ToLower(filepath.Ext(p))] } // isLikelyDirEvent returns true if the event has no file extension (usually a @@ -293,6 +326,9 @@ func collectLocalMedia(log *logrus.Logger) (map[string]*localMediaFile, error) { return nil } ext := strings.ToLower(filepath.Ext(p)) + if codeExtensions[ext] { + return nil + } if !mediaExtensions[ext] { return nil } diff --git a/syncmedia_test.go b/syncmedia_test.go new file mode 100644 index 00000000..331b8c65 --- /dev/null +++ b/syncmedia_test.go @@ -0,0 +1,42 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIsMediaPathAndIsCodePath(t *testing.T) { + mediaCases := []string{ + "content/articles/foo/bar.png", + "content/images/logo.JPG", + "content/pages/about/pic.jpeg", + "content/articles/x/movie.mp4", + "content/articles/x/doc.pdf", + } + for _, p := range mediaCases { + require.Truef(t, isMediaPath(p), "expected media: %s", p) + require.Falsef(t, isCodePath(p), "expected not code: %s", p) + } + + codeCases := []string{ + "content/articles/foo/foo.md", + "content/stylesheets/tailwind.css", + "web/html/_nav.tmpl.html", + "build.go", + "package.json", + "go.mod", + } + for _, p := range codeCases { + require.Truef(t, isCodePath(p), "expected code: %s", p) + require.Falsef(t, isMediaPath(p), "expected not media: %s", p) + } + + // Belt-and-suspenders: even if a code extension were mistakenly added to + // mediaExtensions, isMediaPath must still return false because + // codeExtensions is consulted first. + mediaExtensions[".md"] = true + t.Cleanup(func() { delete(mediaExtensions, ".md") }) + require.False(t, isMediaPath("content/articles/foo/foo.md"), + "code denylist must win over media allowlist") +}