Skip to content

Commit 22cdaac

Browse files
committed
security: fix XSS vulnerabilities by replacing innerHTML with DOM APIs
1 parent 860b6fc commit 22cdaac

File tree

1 file changed

+225
-77
lines changed

1 file changed

+225
-77
lines changed

src/notebooks/deepnote/integrations/integrationWebview.ts

Lines changed: 225 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -368,30 +368,71 @@ export class IntegrationWebviewProvider {
368368
369369
function renderIntegrations() {
370370
const listEl = document.getElementById('integrationList');
371+
372+
// Clear existing content
373+
while (listEl.firstChild) {
374+
listEl.removeChild(listEl.firstChild);
375+
}
376+
371377
if (!integrations || integrations.length === 0) {
372-
listEl.innerHTML = '<p>No integrations found in this project.</p>';
378+
const noIntegrationsMsg = document.createElement('p');
379+
noIntegrationsMsg.textContent = 'No integrations found in this project.';
380+
listEl.appendChild(noIntegrationsMsg);
373381
return;
374382
}
375383
376-
listEl.innerHTML = integrations.map(integration => {
384+
integrations.forEach(integration => {
377385
const statusClass = integration.status === 'connected' ? 'status-connected' : 'status-disconnected';
378386
const statusText = integration.status === 'connected' ? 'Connected' : 'Not Configured';
379387
const configureText = integration.config ? 'Reconfigure' : 'Configure';
380388
const displayName = integration.config?.name || integration.id;
381389
382-
return \`
383-
<div class="integration-item">
384-
<div class="integration-info">
385-
<div class="integration-name">\${displayName}</div>
386-
<div class="integration-status \${statusClass}">\${statusText}</div>
387-
</div>
388-
<div class="integration-actions">
389-
<button data-action="configure" data-id="\${integration.id}">\${configureText}</button>
390-
\${integration.config ? '<button class="secondary" data-action="delete" data-id="'+integration.id+'">Delete</button>' : ''}
391-
</div>
392-
</div>
393-
\`;
394-
}).join('');
390+
// Create item container
391+
const itemDiv = document.createElement('div');
392+
itemDiv.className = 'integration-item';
393+
394+
// Create info section
395+
const infoDiv = document.createElement('div');
396+
infoDiv.className = 'integration-info';
397+
398+
const nameDiv = document.createElement('div');
399+
nameDiv.className = 'integration-name';
400+
nameDiv.textContent = displayName;
401+
402+
const statusDiv = document.createElement('div');
403+
statusDiv.className = 'integration-status';
404+
statusDiv.classList.add(statusClass);
405+
statusDiv.textContent = statusText;
406+
407+
infoDiv.appendChild(nameDiv);
408+
infoDiv.appendChild(statusDiv);
409+
410+
// Create actions section
411+
const actionsDiv = document.createElement('div');
412+
actionsDiv.className = 'integration-actions';
413+
414+
const configureBtn = document.createElement('button');
415+
configureBtn.dataset.action = 'configure';
416+
configureBtn.dataset.id = integration.id;
417+
configureBtn.textContent = configureText;
418+
419+
actionsDiv.appendChild(configureBtn);
420+
421+
// Add delete button if configured
422+
if (integration.config) {
423+
const deleteBtn = document.createElement('button');
424+
deleteBtn.className = 'secondary';
425+
deleteBtn.dataset.action = 'delete';
426+
deleteBtn.dataset.id = integration.id;
427+
deleteBtn.textContent = 'Delete';
428+
actionsDiv.appendChild(deleteBtn);
429+
}
430+
431+
// Assemble the item
432+
itemDiv.appendChild(infoDiv);
433+
itemDiv.appendChild(actionsDiv);
434+
listEl.appendChild(itemDiv);
435+
});
395436
}
396437
397438
// Event delegation for button clicks
@@ -417,29 +458,59 @@ export class IntegrationWebviewProvider {
417458
}
418459
});
419460
461+
function createFormGroup(labelText, inputElement) {
462+
const formGroup = document.createElement('div');
463+
formGroup.className = 'form-group';
464+
465+
const label = document.createElement('label');
466+
label.textContent = labelText;
467+
468+
formGroup.appendChild(label);
469+
formGroup.appendChild(inputElement);
470+
471+
return formGroup;
472+
}
473+
420474
function showConfigurationForm(integrationId, existingConfig) {
421475
currentIntegrationId = integrationId;
422476
const formContainer = document.getElementById('formContainer');
423477
478+
// Clear existing content
479+
while (formContainer.firstChild) {
480+
formContainer.removeChild(formContainer.firstChild);
481+
}
482+
424483
// Determine integration type
425484
let integrationType = existingConfig?.type;
426485
if (!integrationType) {
427486
// Show type selection first
428-
formContainer.innerHTML = \`
429-
<h2>Configure \${integrationId}</h2>
430-
<div class="form-group">
431-
<label>Integration Type:</label>
432-
<select id="integrationType">
433-
<option value="">Select type...</option>
434-
<option value="postgres">PostgreSQL</option>
435-
<option value="bigquery">BigQuery</option>
436-
</select>
437-
</div>
438-
\`;
487+
const heading = document.createElement('h2');
488+
heading.textContent = 'Configure ' + integrationId;
489+
formContainer.appendChild(heading);
490+
491+
const select = document.createElement('select');
492+
select.id = 'integrationType';
493+
494+
const defaultOption = document.createElement('option');
495+
defaultOption.value = '';
496+
defaultOption.textContent = 'Select type...';
497+
select.appendChild(defaultOption);
498+
499+
const postgresOption = document.createElement('option');
500+
postgresOption.value = 'postgres';
501+
postgresOption.textContent = 'PostgreSQL';
502+
select.appendChild(postgresOption);
503+
504+
const bigqueryOption = document.createElement('option');
505+
bigqueryOption.value = 'bigquery';
506+
bigqueryOption.textContent = 'BigQuery';
507+
select.appendChild(bigqueryOption);
508+
509+
formContainer.appendChild(createFormGroup('Integration Type:', select));
439510
formContainer.classList.add('visible');
440511
441512
// Add event listener for type selection
442-
document.getElementById('integrationType').addEventListener('change', (e) => {
513+
select.addEventListener('change', (e) => {
443514
showTypeSpecificForm(e.target.value);
444515
});
445516
return;
@@ -456,58 +527,135 @@ export class IntegrationWebviewProvider {
456527
457528
const formContainer = document.getElementById('formContainer');
458529
530+
// Clear existing content
531+
while (formContainer.firstChild) {
532+
formContainer.removeChild(formContainer.firstChild);
533+
}
534+
459535
if (type === 'postgres') {
460-
formContainer.innerHTML = \`
461-
<h2>Configure PostgreSQL: \${currentIntegrationId}</h2>
462-
<div class="form-group">
463-
<label>Display Name:</label>
464-
<input type="text" id="name" value="\${config?.name || ''}" placeholder="My PostgreSQL Database" required>
465-
</div>
466-
<div class="form-group">
467-
<label>Host:</label>
468-
<input type="text" id="host" value="\${config?.host || ''}" placeholder="localhost" required>
469-
</div>
470-
<div class="form-group">
471-
<label>Port:</label>
472-
<input type="number" id="port" value="\${config?.port || 5432}" placeholder="5432" required>
473-
</div>
474-
<div class="form-group">
475-
<label>Database:</label>
476-
<input type="text" id="database" value="\${config?.database || ''}" placeholder="mydb" required>
477-
</div>
478-
<div class="form-group">
479-
<label>Username:</label>
480-
<input type="text" id="username" value="\${config?.username || ''}" placeholder="postgres" required>
481-
</div>
482-
<div class="form-group">
483-
<label>Password:</label>
484-
<input type="password" id="password" value="\${config?.password || ''}" placeholder="Enter password" required>
485-
</div>
486-
<div class="form-group">
487-
<button data-action="save-postgres">Save</button>
488-
<button class="secondary" data-action="cancel">Cancel</button>
489-
</div>
490-
\`;
536+
const heading = document.createElement('h2');
537+
heading.textContent = 'Configure PostgreSQL: ' + currentIntegrationId;
538+
formContainer.appendChild(heading);
539+
540+
// Display Name
541+
const nameInput = document.createElement('input');
542+
nameInput.type = 'text';
543+
nameInput.id = 'name';
544+
nameInput.value = config?.name || '';
545+
nameInput.placeholder = 'My PostgreSQL Database';
546+
nameInput.required = true;
547+
formContainer.appendChild(createFormGroup('Display Name:', nameInput));
548+
549+
// Host
550+
const hostInput = document.createElement('input');
551+
hostInput.type = 'text';
552+
hostInput.id = 'host';
553+
hostInput.value = config?.host || '';
554+
hostInput.placeholder = 'localhost';
555+
hostInput.required = true;
556+
formContainer.appendChild(createFormGroup('Host:', hostInput));
557+
558+
// Port
559+
const portInput = document.createElement('input');
560+
portInput.type = 'number';
561+
portInput.id = 'port';
562+
portInput.value = config?.port || 5432;
563+
portInput.placeholder = '5432';
564+
portInput.required = true;
565+
formContainer.appendChild(createFormGroup('Port:', portInput));
566+
567+
// Database
568+
const databaseInput = document.createElement('input');
569+
databaseInput.type = 'text';
570+
databaseInput.id = 'database';
571+
databaseInput.value = config?.database || '';
572+
databaseInput.placeholder = 'mydb';
573+
databaseInput.required = true;
574+
formContainer.appendChild(createFormGroup('Database:', databaseInput));
575+
576+
// Username
577+
const usernameInput = document.createElement('input');
578+
usernameInput.type = 'text';
579+
usernameInput.id = 'username';
580+
usernameInput.value = config?.username || '';
581+
usernameInput.placeholder = 'postgres';
582+
usernameInput.required = true;
583+
formContainer.appendChild(createFormGroup('Username:', usernameInput));
584+
585+
// Password
586+
const passwordInput = document.createElement('input');
587+
passwordInput.type = 'password';
588+
passwordInput.id = 'password';
589+
passwordInput.value = config?.password || '';
590+
passwordInput.placeholder = 'Enter password';
591+
passwordInput.required = true;
592+
formContainer.appendChild(createFormGroup('Password:', passwordInput));
593+
594+
// Buttons
595+
const buttonGroup = document.createElement('div');
596+
buttonGroup.className = 'form-group';
597+
598+
const saveBtn = document.createElement('button');
599+
saveBtn.dataset.action = 'save-postgres';
600+
saveBtn.textContent = 'Save';
601+
602+
const cancelBtn = document.createElement('button');
603+
cancelBtn.className = 'secondary';
604+
cancelBtn.dataset.action = 'cancel';
605+
cancelBtn.textContent = 'Cancel';
606+
607+
buttonGroup.appendChild(saveBtn);
608+
buttonGroup.appendChild(cancelBtn);
609+
formContainer.appendChild(buttonGroup);
610+
491611
} else if (type === 'bigquery') {
492-
formContainer.innerHTML = \`
493-
<h2>Configure BigQuery: \${currentIntegrationId}</h2>
494-
<div class="form-group">
495-
<label>Display Name:</label>
496-
<input type="text" id="name" value="\${config?.name || ''}" placeholder="My BigQuery Project" required>
497-
</div>
498-
<div class="form-group">
499-
<label>GCP Project ID:</label>
500-
<input type="text" id="projectId" value="\${config?.projectId || ''}" placeholder="my-gcp-project" required>
501-
</div>
502-
<div class="form-group">
503-
<label>Service Account Credentials (JSON):</label>
504-
<textarea id="credentials" rows="10" placeholder="Paste service account JSON here" required>\${config?.credentials || ''}</textarea>
505-
</div>
506-
<div class="form-group">
507-
<button data-action="save-bigquery">Save</button>
508-
<button class="secondary" data-action="cancel">Cancel</button>
509-
</div>
510-
\`;
612+
const heading = document.createElement('h2');
613+
heading.textContent = 'Configure BigQuery: ' + currentIntegrationId;
614+
formContainer.appendChild(heading);
615+
616+
// Display Name
617+
const nameInput = document.createElement('input');
618+
nameInput.type = 'text';
619+
nameInput.id = 'name';
620+
nameInput.value = config?.name || '';
621+
nameInput.placeholder = 'My BigQuery Project';
622+
nameInput.required = true;
623+
formContainer.appendChild(createFormGroup('Display Name:', nameInput));
624+
625+
// GCP Project ID
626+
const projectIdInput = document.createElement('input');
627+
projectIdInput.type = 'text';
628+
projectIdInput.id = 'projectId';
629+
projectIdInput.value = config?.projectId || '';
630+
projectIdInput.placeholder = 'my-gcp-project';
631+
projectIdInput.required = true;
632+
formContainer.appendChild(createFormGroup('GCP Project ID:', projectIdInput));
633+
634+
// Service Account Credentials
635+
const credentialsTextarea = document.createElement('textarea');
636+
credentialsTextarea.id = 'credentials';
637+
credentialsTextarea.rows = 10;
638+
credentialsTextarea.placeholder = 'Paste service account JSON here';
639+
credentialsTextarea.required = true;
640+
credentialsTextarea.value = config?.credentials || '';
641+
formContainer.appendChild(createFormGroup('Service Account Credentials (JSON):', credentialsTextarea));
642+
643+
// Buttons
644+
const buttonGroup = document.createElement('div');
645+
buttonGroup.className = 'form-group';
646+
647+
const saveBtn = document.createElement('button');
648+
saveBtn.dataset.action = 'save-bigquery';
649+
saveBtn.textContent = 'Save';
650+
651+
const cancelBtn = document.createElement('button');
652+
cancelBtn.className = 'secondary';
653+
cancelBtn.dataset.action = 'cancel';
654+
cancelBtn.textContent = 'Cancel';
655+
656+
buttonGroup.appendChild(saveBtn);
657+
buttonGroup.appendChild(cancelBtn);
658+
formContainer.appendChild(buttonGroup);
511659
}
512660
513661
formContainer.classList.add('visible');

0 commit comments

Comments
 (0)