Skip to content

Commit b853e04

Browse files
fix: [UIE-10152] - Fix html injection vuln in open Supprt Ticket and Quotas Increase Form.
1 parent 5f79e44 commit b853e04

File tree

2 files changed

+63
-8
lines changed

2 files changed

+63
-8
lines changed

packages/manager/src/features/Account/Quotas/QuotasIncreaseForm.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as React from 'react';
1414
import { Controller, FormProvider, useForm } from 'react-hook-form';
1515

1616
import { Markdown } from 'src/components/Markdown/Markdown';
17+
import { sanitizeHTML } from 'src/utilities/sanitizeHTML';
1718

1819
import { getQuotaIncreaseFormSchema, getQuotaIncreaseMessage } from './utils';
1920

@@ -77,14 +78,34 @@ export const QuotasIncreaseForm = (props: QuotasIncreaseFormProps) => {
7778
selectedService,
7879
}).description;
7980

81+
/**
82+
* Sanitizes user input to prevent HTML injection attacks
83+
*/
84+
const sanitizeInput = (input: string): string => {
85+
if (!input) return '';
86+
return sanitizeHTML({
87+
sanitizingTier: 'strict',
88+
text: input,
89+
sanitizeOptions: {
90+
ALLOWED_TAGS: [],
91+
ALLOWED_ATTR: [],
92+
KEEP_CONTENT: true,
93+
},
94+
});
95+
};
96+
8097
const handleSubmit = form.handleSubmit(async (values) => {
8198
const { onSuccess } = props;
8299

83100
setSubmitting(true);
84101

102+
// Sanitize user inputs to prevent HTML injection
103+
const sanitizedSummary = sanitizeInput(values.summary);
104+
const sanitizedNotes = sanitizeInput(values.notes);
105+
85106
const payload: TicketRequest = {
86-
description: `${quotaIncreaseDescription}\n\n${values.notes}`,
87-
summary: values.summary,
107+
description: `${quotaIncreaseDescription}\n\n${sanitizedNotes}`,
108+
summary: sanitizedSummary,
88109
};
89110

90111
createSupportTicket(payload)
@@ -254,7 +275,7 @@ export const QuotasIncreaseForm = (props: QuotasIncreaseFormProps) => {
254275
{summary}
255276
</Typography>
256277
<Markdown textOrMarkdown={quotaIncreaseDescription} />{' '}
257-
<Markdown textOrMarkdown={notes ?? ''} />
278+
<Markdown textOrMarkdown={sanitizeInput(notes ?? '')} />
258279
</Stack>
259280
</Accordion>
260281
<ActionsPanel

packages/manager/src/features/Support/SupportTickets/SupportTicketDialog.tsx

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { debounce } from 'throttle-debounce';
2020

2121
import { sendSupportTicketExitEvent } from 'src/utilities/analytics/customEventAnalytics';
2222
import { getErrorStringOrDefault } from 'src/utilities/errorUtils';
23+
import { sanitizeHTML } from 'src/utilities/sanitizeHTML';
2324
import { storage, supportTicketStorageDefaults } from 'src/utilities/storage';
2425

2526
import { AttachFileForm } from '../AttachFileForm';
@@ -139,6 +140,30 @@ export const getInitialValue = (
139140
return fromProps ?? fromStorage ?? '';
140141
};
141142

143+
/**
144+
* Sanitizes user input to prevent HTML injection attacks
145+
* Uses the centralized sanitizeHTML utility with strict settings
146+
*
147+
* @param input - The user input string to sanitize
148+
* @returns Sanitized plain text string with all HTML removed
149+
*/
150+
const sanitizeInput = (input: string): string => {
151+
if (!input) return '';
152+
153+
// Use strict sanitization: no HTML tags allowed
154+
const sanitized = sanitizeHTML({
155+
sanitizingTier: 'strict',
156+
text: input,
157+
sanitizeOptions: {
158+
ALLOWED_TAGS: [], // Strip all HTML tags
159+
ALLOWED_ATTR: [], // Strip all attributes
160+
KEEP_CONTENT: true, // Keep text content
161+
},
162+
});
163+
164+
return sanitized;
165+
};
166+
142167
export const SupportTicketDialog = (props: SupportTicketDialogProps) => {
143168
const {
144169
open,
@@ -349,13 +374,22 @@ export const SupportTicketDialog = (props: SupportTicketDialogProps) => {
349374
const handleSubmit = form.handleSubmit(async (values) => {
350375
const { onSuccess } = props;
351376

352-
const _description = formatDescription(values, ticketType);
377+
// Sanitize all string fields to prevent HTML injection
378+
const sanitizedValues = Object.fromEntries(
379+
Object.entries(values).map(([key, value]) => [
380+
key,
381+
typeof value === 'string' ? sanitizeInput(value) : value,
382+
])
383+
) as typeof values;
384+
385+
const _description = formatDescription(sanitizedValues, ticketType);
353386

354387
// If this is an account limit ticket, we needed the entity type but won't actually send a valid entity selection.
355388
// Reset the entity type and id back to defaults.
356389
const _entityType =
357-
ticketType === 'accountLimit' ? 'general' : values.entityType;
358-
const _entityId = ticketType === 'accountLimit' ? '' : values.entityId;
390+
ticketType === 'accountLimit' ? 'general' : sanitizedValues.entityType;
391+
const _entityId =
392+
ticketType === 'accountLimit' ? '' : sanitizedValues.entityId;
359393

360394
if (!['general', 'none'].includes(_entityType) && !_entityId) {
361395
form.setError('entityId', {
@@ -369,12 +403,12 @@ export const SupportTicketDialog = (props: SupportTicketDialogProps) => {
369403
const baseRequestPayload = {
370404
description: _description,
371405
severity: selectedSeverity,
372-
summary,
406+
summary: sanitizedValues.summary,
373407
};
374408

375409
let requestPayload;
376410
if (entityType === 'bucket') {
377-
const bucketLabel = values.entityInputValue;
411+
const bucketLabel = sanitizedValues.entityInputValue;
378412
requestPayload = {
379413
bucket: bucketLabel,
380414
region: _entityId,

0 commit comments

Comments
 (0)