Merge pull request 'Fix Decap image filename validation' (#2) from review/decap-image-validation into main

Reviewed-on: SloanOps/3031023-xyz#2
This commit is contained in:
2026-09-04 16:02:27 +00:00
3 changed files with 16 additions and 8 deletions
+6
View File
@@ -156,6 +156,12 @@ not accept executables, archives, HTML, scripts, or SVG uploads. Asset rights,
alt text, crop, dimensions, and optimization remain operator review items even
when the CMS accepts an upload.
Decap's image widget applies its field `pattern` to the selected file's base
name, not to the repository-relative media path. Keep that UI pattern limited
to the approved raster filename extensions; enforce the full
`public/images/uploads` path separately in the schema, verifier, and Gitea
branch rules below.
The media gallery lists only `public/images/uploads`, so the pilot seeds that
directory with the images referenced by CMS-controlled fields. Removing an
image from content clears the field and uses the page's text-only or initials
+7 -7
View File
@@ -144,7 +144,7 @@ collections:
required: false
choose_url: false
allow_multiple: false
pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
media_library:
config:
max_file_size: 512000
@@ -181,7 +181,7 @@ collections:
required: false
choose_url: false
allow_multiple: false
pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
media_library:
config: { max_file_size: 512000, multiple: false }
- label: Home hero image description
@@ -194,7 +194,7 @@ collections:
required: false
choose_url: false
allow_multiple: false
pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
media_library:
config: { max_file_size: 512000, multiple: false }
- label: Home story image description
@@ -207,7 +207,7 @@ collections:
required: false
choose_url: false
allow_multiple: false
pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
media_library:
config: { max_file_size: 512000, multiple: false }
- label: About hero image description
@@ -220,7 +220,7 @@ collections:
required: false
choose_url: false
allow_multiple: false
pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
media_library:
config: { max_file_size: 512000, multiple: false }
- label: About story image description
@@ -233,7 +233,7 @@ collections:
required: false
choose_url: false
allow_multiple: false
pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
media_library:
config: { max_file_size: 512000, multiple: false }
- label: Services hero image description
@@ -246,7 +246,7 @@ collections:
required: false
choose_url: false
allow_multiple: false
pattern: ['^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
pattern: ['^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp|avif)$', 'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.']
media_library:
config: { max_file_size: 512000, multiple: false }
- label: Contact hero image description
+3 -1
View File
@@ -45,7 +45,7 @@ const markdownField = {
editor_components: [],
};
const cmsImagePattern = [
'^/images/uploads/[A-Za-z0-9][A-Za-z0-9._-]*\\.(?:jpe?g|png|webp|avif)$',
'^[A-Za-z0-9][A-Za-z0-9._-]*\\.(?:jpe?g|png|webp|avif)$',
'Choose an approved JPG, PNG, WebP, or AVIF upload. SVG and general files are not allowed.',
];
const optionalImageField = {
@@ -160,6 +160,8 @@ allFields.forEach(visitField);
const imageFields = allFields.filter(({ widget }) => widget === 'image');
assert(imageFields.length === 7, 'Only the team image and six fixed page-image fields may expose media selection.');
for (const field of imageFields) expectSubset(field, optionalImageField, `image field ${field.name}`);
assert(new RegExp(cmsImagePattern[0]).test('example-photo.jpg'), 'Image widget validation must accept the filename Decap passes to field patterns.');
assert(!new RegExp(cmsImagePattern[0]).test('example-photo.svg'), 'Image widget validation must reject SVG filenames.');
const schema = await readFile('src/content.config.ts', 'utf8');
for (const fragment of [