From 4bc7598a434a45048c8aa9b4d884e1c0152d684a Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Fri, 13 Nov 2026 21:42:40 +0100 Subject: [PATCH] admin: escape dynamic text in S3 bucket modals (#12625) admin: escape dynamic text in bucket modals --- weed/admin/view/app/admin_render_test.go | 20 ++++++++++++++++++++ weed/admin/view/app/s3_buckets.templ | 14 +++++++------- weed/admin/view/app/s3_buckets_templ.go | 2 +- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/weed/admin/view/app/admin_render_test.go b/weed/admin/view/app/admin_render_test.go index bd4318a4bb..31bc571abc 100644 --- a/weed/admin/view/app/admin_render_test.go +++ b/weed/admin/view/app/admin_render_test.go @@ -33,6 +33,26 @@ func TestAdminDashboardShowsDiskUsage(t *testing.T) { } } +func TestS3BucketModalsEscapeDynamicHTMLText(t *testing.T) { + var buf bytes.Buffer + if err := S3Buckets(dash.S3BucketsData{}).Render(context.Background(), &buf); err != nil { + t.Fatalf("render bucket page: %v", err) + } + + for _, want := range []string{ + "Bucket Details - ' + escapeHtml(bucketName)", + "Lifecycle - ' + escapeHtml(bucketName)", + "Error loading bucket details: ' + escapeHtml(data.error)", + "Error loading bucket details: ' + escapeHtml(error.message)", + "Error loading lifecycle rules: ' + escapeHtml(data.error || 'unknown error')", + "Error loading lifecycle rules: ' + escapeHtml(error.message)", + } { + if !strings.Contains(buf.String(), want) { + t.Errorf("rendered bucket page is missing escaped text expression %q", want) + } + } +} + func TestCalculatePercent64PetabyteScale(t *testing.T) { const pib = int64(1) << 50 if got := calculatePercent64(100*pib, 200*pib); got != 50 { diff --git a/weed/admin/view/app/s3_buckets.templ b/weed/admin/view/app/s3_buckets.templ index a8fc1b416d..bb78a59146 100644 --- a/weed/admin/view/app/s3_buckets.templ +++ b/weed/admin/view/app/s3_buckets.templ @@ -1181,7 +1181,7 @@ templ S3Buckets(data dash.S3BucketsData) { // Update modal title document.getElementById('bucketDetailsModalLabel').innerHTML = - 'Bucket Details - ' + bucketName; + 'Bucket Details - ' + escapeHtml(bucketName); // Show loading spinner document.getElementById('bucketDetailsContent').innerHTML = @@ -1202,7 +1202,7 @@ templ S3Buckets(data dash.S3BucketsData) { document.getElementById('bucketDetailsContent').innerHTML = '
' + '' + - 'Error loading bucket details: ' + data.error + + 'Error loading bucket details: ' + escapeHtml(data.error) + '<\\/div>'; } else { displayBucketDetails(data); @@ -1213,7 +1213,7 @@ templ S3Buckets(data dash.S3BucketsData) { document.getElementById('bucketDetailsContent').innerHTML = '
' + '' + - 'Error loading bucket details: ' + error.message + + 'Error loading bucket details: ' + escapeHtml(error.message) + '<\\/div>'; }); }); @@ -1229,7 +1229,7 @@ templ S3Buckets(data dash.S3BucketsData) { lifecycleEditor = { bucket: null, rules: [], editingIndex: null, editingRule: null, rawXml: '', hasTransition: false }; document.getElementById('bucketLifecycleModalLabel').innerHTML = - 'Lifecycle - ' + bucketName; + 'Lifecycle - ' + escapeHtml(bucketName); document.getElementById('bucketLifecycleContent').innerHTML = '
' + @@ -1251,7 +1251,7 @@ templ S3Buckets(data dash.S3BucketsData) { document.getElementById('bucketLifecycleContent').innerHTML = '
' + '' + - 'Error loading lifecycle rules: ' + (data.error || 'unknown error') + + 'Error loading lifecycle rules: ' + escapeHtml(data.error || 'unknown error') + '<\/div>'; } else { displayBucketLifecycle(data); @@ -1263,7 +1263,7 @@ templ S3Buckets(data dash.S3BucketsData) { document.getElementById('bucketLifecycleContent').innerHTML = '
' + '' + - 'Error loading lifecycle rules: ' + error.message + + 'Error loading lifecycle rules: ' + escapeHtml(error.message) + '<\/div>'; }); }); @@ -2244,4 +2244,4 @@ func getQuotaInMB(quotaBytes int64) int64 { quotaBytes = -quotaBytes // Handle disabled quotas (negative values) } return quotaBytes / (1024 * 1024) -} \ No newline at end of file +} diff --git a/weed/admin/view/app/s3_buckets_templ.go b/weed/admin/view/app/s3_buckets_templ.go index 8f7abee7d6..93709be7ec 100644 --- a/weed/admin/view/app/s3_buckets_templ.go +++ b/weed/admin/view/app/s3_buckets_templ.go @@ -880,7 +880,7 @@ func S3Buckets(data dash.S3BucketsData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "
Bucket Policy
Loading...
Loading bucket policy...

Every statement must specify a Principal, and every Resource must refer to this bucket. Leave the document empty and click Delete to remove the policy.

Enter the bucket policy document as JSON
Manage Bucket Owner
Select the S3 identity that owns this bucket. Non-admin users can only access buckets they own.
Loading users...
Loading users...
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "
Bucket Policy
Loading...
Loading bucket policy...

Every statement must specify a Principal, and every Resource must refer to this bucket. Leave the document empty and click Delete to remove the policy.

Enter the bucket policy document as JSON
Manage Bucket Owner
Select the S3 identity that owns this bucket. Non-admin users can only access buckets they own.
Loading users...
Loading users...
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }