diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fea70b87..01352005 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -48,7 +48,7 @@ jobs: matrix: include: - group: core - files: "tests/e2e/scenarios/test_connection.py tests/e2e/scenarios/test_chat.py tests/e2e/scenarios/test_sse_reconnect.py tests/e2e/scenarios/test_html_injection.py" + files: "tests/e2e/scenarios/test_connection.py tests/e2e/scenarios/test_chat.py tests/e2e/scenarios/test_sse_reconnect.py tests/e2e/scenarios/test_html_injection.py tests/e2e/scenarios/test_csp.py" - group: features files: "tests/e2e/scenarios/test_skills.py tests/e2e/scenarios/test_tool_approval.py" - group: extensions diff --git a/CHANGELOG.md b/CHANGELOG.md index fcdcd349..36c4d103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.18.0](https://github.com/nearai/ironclaw/compare/v0.17.0...v0.18.0) - 2026-03-11 + +### Other + +- Merge pull request #907 from nearai/staging-promote/b0214fef-22930316561 +- promote staging to main (2026-03-10 15:19 UTC) ([#865](https://github.com/nearai/ironclaw/pull/865)) +- Merge pull request #830 from nearai/staging-promote/3a2989d0-22888378864 +- update WASM artifact SHA256 checksums [skip ci] ([#876](https://github.com/nearai/ironclaw/pull/876)) + ## [0.17.0](https://github.com/nearai/ironclaw/compare/v0.16.1...v0.17.0) - 2026-03-10 ### Added diff --git a/Cargo.lock b/Cargo.lock index 1f62e7d8..c6b3e6f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3350,7 +3350,7 @@ dependencies = [ [[package]] name = "ironclaw" -version = "0.17.0" +version = "0.18.0" dependencies = [ "aes-gcm", "aho-corasick", diff --git a/Cargo.toml b/Cargo.toml index 5610eb51..c6065dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ exclude = [ [package] name = "ironclaw" -version = "0.17.0" +version = "0.18.0" edition = "2024" rust-version = "1.92" description = "Secure personal AI assistant that protects your data and expands its capabilities on the fly" diff --git a/src/channels/web/static/app.js b/src/channels/web/static/app.js index de1f83b6..f5812030 100644 --- a/src/channels/web/static/app.js +++ b/src/channels/web/static/app.js @@ -670,7 +670,7 @@ function renderMarkdown(text) { // Sanitize HTML output to prevent XSS from tool output or LLM responses. html = sanitizeRenderedHtml(html); // Inject copy buttons into
 blocks
-    html = html.replace(/
/g, '
');
+    html = html.replace(/
/g, '
');
     return html;
   }
   return escapeHtml(text);
@@ -1819,13 +1819,11 @@ function saveMemoryEdit() {
 
 function buildBreadcrumb(path) {
   const parts = path.split('/');
-  let html = 'workspace';
+  let html = 'workspace';
   let current = '';
   for (const part of parts) {
     current += (current ? '/' : '') + part;
-    // Store the path in data-path (HTML-escaped) and read it back via this.dataset.path
-    // to avoid single-quote injection in inline JS string literals.
-    html += ' / ' + escapeHtml(part) + '';
+    html += ' / ' + escapeHtml(part) + '';
   }
   return html;
 }
@@ -2795,11 +2793,11 @@ function renderJobsList(jobs) {
 
     let actionBtns = '';
     if (job.state === 'pending' || job.state === 'in_progress') {
-      actionBtns = '';
+      actionBtns = '';
     }
     // Retry is only shown in the detail view where can_restart is available.
 
-    return ''
+    return ''
       + '' + shortId + ''
       + '' + escapeHtml(job.title) + ''
       + '' + escapeHtml(job.state) + ''
@@ -2862,12 +2860,12 @@ function renderJobDetail(job) {
   const header = document.createElement('div');
   header.className = 'job-detail-header';
 
-  let headerHtml = ''
+  let headerHtml = ''
     + '

' + escapeHtml(job.title) + '

' + '' + escapeHtml(job.state) + ''; if ((job.state === 'failed' || job.state === 'interrupted') && job.can_restart === true) { - headerHtml += ''; + headerHtml += ''; } if (job.browse_url) { headerHtml += 'Browse Files'; @@ -3324,7 +3322,7 @@ function renderRoutinesList(routines) { const toggleLabel = r.enabled ? 'Disable' : 'Enable'; const toggleClass = r.enabled ? 'btn-cancel' : 'btn-restart'; - return '' + return '' + '' + escapeHtml(r.name) + '' + '' + escapeHtml(r.trigger_summary) + '' + '' + escapeHtml(r.action_type) + '' @@ -3333,9 +3331,9 @@ function renderRoutinesList(routines) { + '' + r.run_count + '' + '' + escapeHtml(r.status) + '' + '' - + ' ' - + ' ' - + '' + + ' ' + + ' ' + + '' + '' + ''; }).join(''); @@ -3371,7 +3369,7 @@ function renderRoutineDetail(routine) { : 'active'; let html = '
' - + '' + + '' + '

' + escapeHtml(routine.name) + '

' + '' + escapeHtml(statusLabel) + '' + '
'; @@ -3418,7 +3416,7 @@ function renderRoutineDetail(routine) { + '' + formatDate(run.completed_at) + '' + '' + escapeHtml(run.status) + '' + '' + escapeHtml(run.result_summary || '-') - + (run.job_id ? ' [view job]' : '') + + (run.job_id ? ' [view job]' : '') + '' + '' + (run.tokens_used != null ? run.tokens_used : '-') + '' + ''; @@ -3661,7 +3659,7 @@ function renderTeePopover(report) { + '
VM Config
' + '
' + escapeHtml(vmConfig) + '
' + '
' - + '
'; + + ''; } function copyTeeReport() { @@ -4143,3 +4141,94 @@ function formatDate(isoString) { const d = new Date(isoString); return d.toLocaleString(); } + +// --- Event Listener Registration (CSP-safe, no inline handlers) --- + +document.getElementById('auth-connect-btn').addEventListener('click', () => authenticate()); +document.getElementById('restart-overlay').addEventListener('click', () => cancelRestart()); +document.getElementById('restart-close-btn').addEventListener('click', () => cancelRestart()); +document.getElementById('restart-cancel-btn').addEventListener('click', () => cancelRestart()); +document.getElementById('restart-confirm-btn').addEventListener('click', () => confirmRestart()); +document.getElementById('restart-btn').addEventListener('click', () => triggerRestart()); +document.getElementById('thread-new-btn').addEventListener('click', () => createNewThread()); +document.getElementById('thread-toggle-btn').addEventListener('click', () => toggleThreadSidebar()); +document.getElementById('assistant-thread').addEventListener('click', () => switchToAssistant()); +document.getElementById('send-btn').addEventListener('click', () => sendMessage()); +document.getElementById('memory-edit-btn').addEventListener('click', () => startMemoryEdit()); +document.getElementById('memory-save-btn').addEventListener('click', () => saveMemoryEdit()); +document.getElementById('memory-cancel-btn').addEventListener('click', () => cancelMemoryEdit()); +document.getElementById('logs-server-level').addEventListener('change', (e) => setServerLogLevel(e.target.value)); +document.getElementById('logs-pause-btn').addEventListener('click', () => toggleLogsPause()); +document.getElementById('logs-clear-btn').addEventListener('click', () => clearLogs()); +document.getElementById('wasm-install-btn').addEventListener('click', () => installWasmExtension()); +document.getElementById('mcp-add-btn').addEventListener('click', () => addMcpServer()); +document.getElementById('skill-search-btn').addEventListener('click', () => searchClawHub()); +document.getElementById('skill-install-btn').addEventListener('click', () => installSkillFromForm()); + +// --- Delegated Event Handlers (for dynamically generated HTML) --- + +document.addEventListener('click', function(e) { + const el = e.target.closest('[data-action]'); + if (!el) return; + const action = el.dataset.action; + + switch (action) { + case 'copy-code': + copyCodeBlock(el); + break; + case 'breadcrumb-root': + e.preventDefault(); + loadMemoryTree(); + break; + case 'breadcrumb-file': + e.preventDefault(); + readMemoryFile(el.dataset.path); + break; + case 'cancel-job': + e.stopPropagation(); + cancelJob(el.dataset.id); + break; + case 'open-job': + openJobDetail(el.dataset.id); + break; + case 'close-job-detail': + closeJobDetail(); + break; + case 'restart-job': + restartJob(el.dataset.id); + break; + case 'open-routine': + openRoutineDetail(el.dataset.id); + break; + case 'toggle-routine': + e.stopPropagation(); + toggleRoutine(el.dataset.id); + break; + case 'trigger-routine': + e.stopPropagation(); + triggerRoutine(el.dataset.id); + break; + case 'delete-routine': + e.stopPropagation(); + deleteRoutine(el.dataset.id, el.dataset.name); + break; + case 'close-routine-detail': + closeRoutineDetail(); + break; + case 'view-run-job': + e.preventDefault(); + switchTab('jobs'); + openJobDetail(el.dataset.id); + break; + case 'copy-tee-report': + copyTeeReport(); + break; + case 'switch-language': + if (typeof switchLanguage === 'function') switchLanguage(el.dataset.lang); + break; + } +}); + +document.getElementById('language-btn').addEventListener('click', function() { + if (typeof toggleLanguageMenu === 'function') toggleLanguageMenu(); +}); diff --git a/src/channels/web/static/index.html b/src/channels/web/static/index.html index b6dd9d3a..e0a4ae07 100644 --- a/src/channels/web/static/index.html +++ b/src/channels/web/static/index.html @@ -9,12 +9,12 @@ - + - +