Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
aa2f2a3
perf: replace synchronous file check with async in native worker
google-labs-jules[bot] Feb 7, 2026
b2ef7f6
test: add serialization tests for ModificationTracker
google-labs-jules[bot] Feb 7, 2026
e243329
Refactor cancelTokenToAbortSignal to src/core/cancellation-utils.ts a…
google-labs-jules[bot] Feb 7, 2026
b80a0a9
Refactor WebviewCollection to be testable and add unit tests
google-labs-jules[bot] Feb 7, 2026
1319f5c
test: add unit tests for getUriParts utility
google-labs-jules[bot] Feb 7, 2026
8460a23
Optimize undo deletion with batch insertions
google-labs-jules[bot] Feb 7, 2026
46930ea
feat(perf): batch fetch pragmas to reduce IPC overhead
google-labs-jules[bot] Feb 7, 2026
1669d10
feat: improve batch update performance in HostBridge
google-labs-jules[bot] Feb 7, 2026
0e128eb
feat: Validate table/view existence in virtual file system
google-labs-jules[bot] Feb 7, 2026
a00b4ff
feat: Optimize JSON patch implementation with native SQLite function
google-labs-jules[bot] Feb 7, 2026
9b25642
fix: prevent stack overflow in JSON merge patch generation
google-labs-jules[bot] Feb 7, 2026
b5df429
⚡ Optimize Undo/Redo Performance with Batch Cell Updates
google-labs-jules[bot] Feb 7, 2026
67d8170
fix: implement memory limit for undo history
google-labs-jules[bot] Feb 7, 2026
c85c322
feat(core): implement query timeout in WASM worker to prevent DoS
google-labs-jules[bot] Feb 7, 2026
8ebbd9a
test: Add unit tests for WasmDatabaseEngine.updateCellBatch
google-labs-jules[bot] Feb 7, 2026
4abf557
Test: Add unit tests for WasmDatabaseEngine.addColumn
google-labs-jules[bot] Feb 7, 2026
cff8753
perf: Use async file read in sqlite-db to avoid blocking event loop
google-labs-jules[bot] Feb 7, 2026
b4821ac
fix(security): securely escape NUL bytes in SQL export strings
google-labs-jules[bot] Feb 7, 2026
6a30d51
test: improve coverage for toDatasetAttrs by refactoring to html-utils
google-labs-jules[bot] Feb 7, 2026
a0a697f
feat(test): add unit tests for SQLiteFileSystemProvider.writeFile
google-labs-jules[bot] Feb 7, 2026
596c928
Remove 'unsafe-inline' from style-src CSP
google-labs-jules[bot] Feb 7, 2026
121770e
Refactor undoModification in WasmDatabaseEngine
google-labs-jules[bot] Feb 7, 2026
3df15f9
Refactor query builder to remove duplicated filter logic
google-labs-jules[bot] Feb 7, 2026
1de01aa
Refactor resolveCustomEditor for better maintainability
google-labs-jules[bot] Feb 7, 2026
0fd1ecf
Refactor worker endpoint to remove boilerplate and fix RPC serialization
google-labs-jules[bot] Feb 7, 2026
3eb1366
Refactor updateCell to use updateCellBatch in SQLite engine
google-labs-jules[bot] Feb 7, 2026
d1f6d44
Refactor WasmDatabaseInstance to use typed prepared statements
google-labs-jules[bot] Feb 7, 2026
7aaf053
Refactor BlobInspector to use backendApi consistently
google-labs-jules[bot] Feb 7, 2026
5b58ca0
feat(test): add unit tests for SQLiteFileSystemProvider.readFile
google-labs-jules[bot] Feb 7, 2026
6d52660
Merge test-tracker-serialization-5611597630610815347
zknpr Feb 7, 2026
999eeb2
Merge testing-improvement-cancellation-token-6991125888385650508
zknpr Feb 7, 2026
7bd3b67
Merge testing-webview-collection-6092940338158183239
zknpr Feb 7, 2026
ac7294e
Merge jules-test-geturiparts-3659151931186356262
zknpr Feb 7, 2026
dce60ee
Merge jules-perf-undo-batch-insert-1012390901274410963
zknpr Feb 7, 2026
a3f5eb1
Merge optimize-pragma-fetching-2295165269245791039
zknpr Feb 7, 2026
56d1707
Merge update-batch-performance-7821265999889062770
zknpr Feb 7, 2026
4c34693
Merge jules-validate-table-existence-10056005998161266617
zknpr Feb 7, 2026
9f6bb43
Merge json-patch-optimization-12088301536056168967
zknpr Feb 7, 2026
950e8d2
Merge json-merge-patch-recursion-fix-10449259625165348147
zknpr Feb 7, 2026
dc875ff
Merge perf/undo-batch-optimization-10475408086369639529
zknpr Feb 7, 2026
87e651f
Merge fix-undo-history-memory-leak-7224620587777115643
zknpr Feb 7, 2026
76061cf
Merge wasm-timeout-fix (resolved conflicts)
zknpr Feb 7, 2026
637b9b5
Refactor RPC to use safe Transferable[] instead of any[]
google-labs-jules[bot] Feb 7, 2026
9288edd
Merge wasm-db-engine-tests-16518766468019068459
zknpr Feb 7, 2026
7ddfbea
Merge test/wasm-db-add-column-5177981917518138462
zknpr Feb 7, 2026
ee5398a
Merge jules-perf-async-file-read-8887365914515574585
zknpr Feb 7, 2026
8991a35
Merge security-fix-nul-escaping-14384620162266841219
zknpr Feb 7, 2026
b014790
Merge test-improvement-todatasetattrs (resolved conflicts)
zknpr Feb 7, 2026
0257abe
Merge testing-improvement-file-system-provider (resolved conflicts)
zknpr Feb 7, 2026
561f2ff
Merge remove-unsafe-inline-styles-6052571533312156332
zknpr Feb 7, 2026
77eac7b
Merge refactor-undo-modification (resolved conflicts, keeping refacto…
zknpr Feb 7, 2026
af545f7
Merge refactor-query-builder-duplication-9019015668633560601
zknpr Feb 7, 2026
c01e85b
Merge code-health-refactor-editor-controller-15064859121377556669
zknpr Feb 7, 2026
dd16537
Merge refactor-worker-endpoint-14553857305606594836
zknpr Feb 7, 2026
3dc74de
Merge refactor-update-cell-batch (keeping extracted method structure …
zknpr Feb 7, 2026
4e392c8
Merge refactor/wasm-db-types-1782077784216029526
zknpr Feb 7, 2026
8b699b8
Merge refactor-blob-inspector-backendapi-11995963540439821959
zknpr Feb 7, 2026
880d71d
Merge testing-improvement-readfile (take incoming readFile tests)
zknpr Feb 7, 2026
ab09665
Fix: Apply security fixes and memory leak fixes
zknpr Feb 7, 2026
4774055
Merge PR #64: Improve type safety in RPC transfer lists
zknpr Feb 7, 2026
6f4a9c5
Remove conflicting test file from PR #59 (not fully merged)
zknpr Feb 7, 2026
152a4f6
Fix test failures and update CHANGELOG for v1.3.0
zknpr Feb 7, 2026
39e4e20
feat(core): Re-implement query timeout using iterateStatements
zknpr Feb 7, 2026
18b7d89
feat(config): Add configurable settings for query timeout and undo me…
zknpr Feb 7, 2026
58f5d33
docs(CLAUDE.md): Update configuration table and add Gotchas section
zknpr Feb 7, 2026
c11366b
fix(csp): Add nonce to style tag for CSP compliance
zknpr Feb 7, 2026
20dc954
fix(grid): Dynamic row number column width for large datasets
zknpr Feb 7, 2026
f3182f7
feat(deleteColumns): Add confirmation dialog for dependent indexes
zknpr Feb 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
# Changelog

## 1.3.0

### Security

- **SQL Wildcard Injection Prevention**: Added `escapeLikePattern()` function to escape `%`, `_`, and `\` characters in LIKE queries. Prevents attackers from crafting inputs that cause expensive full table scans or bypass filters. All filter queries now use `ESCAPE '\\'` clause.
- **NUL Byte Escaping**: Fixed potential SQL injection via NUL characters in exported SQL strings. Strings containing `\0` are now encoded as hex blobs with `CAST(X'...' AS TEXT)`.
- **JSON Merge Patch Stack Overflow**: Added `MAX_DEPTH=1000` limit to prevent stack overflow from malicious or deeply nested JSON data.
- **Unbounded Undo History Memory**: Added `maxMemory` limit (50MB default) to undo history to prevent memory exhaustion on long editing sessions.
- **CSP Hardening**: Removed unsafe inline styles from webview HTML, extracting 20+ inline styles to CSS classes for stricter Content Security Policy compliance.

### Performance

- **Query Timeout Protection**: Added 30-second query timeout using `iterateStatements` API. Prevents runaway queries from freezing the extension. Timeout is checked during row iteration for interruptible execution.
- **Async File Operations**: Converted synchronous `fs.existsSync` and `readFileSync` calls to async equivalents in native worker, preventing main thread blocking.
- **Batch Undo Operations**: Undo/redo for batch cell updates now uses `updateCellBatch` instead of individual transactions, significantly improving performance.
- **Batch Row Insertions**: Added `insertRowBatch` for bulk row operations, respecting SQLite's 999 parameter limit.
- **Optimized Pragma Fetching**: Added `queryBatch` for fetching multiple pragmas in a single IPC round-trip.
- **Native JSON Patch**: Uses SQLite's native `json_patch()` function when available, with JS fallback for older versions.

### Improvements

- **Type Safety**: Replaced `any[]` with proper `Transferable[]` types throughout the RPC layer, removing `@ts-ignore` comments.
- **Memory Leak Fix**: Fixed listener leak in `cancelTokenToAbortSignal` by properly disposing the cancellation listener after abort.
- **Table Existence Validation**: Virtual file system now validates table/view existence before attempting cell reads.
- **Configurable Query Timeout**: Added `sqliteExplorer.queryTimeout` setting (default 30s) to control query execution timeout.
- **Configurable Undo Memory**: Added `sqliteExplorer.maxUndoMemory` setting (default 50MB) to control undo/redo history memory limit.
- **Full CSP Compliance**: Removed all `'unsafe-inline'` usage from both scripts and styles. Dynamic styles now use CSSOM which is CSP-compliant.

### Refactoring

- **Extracted Serialization Module**: Moved RPC serialization utilities to `src/core/serialization.ts` for reuse.
- **WebviewMessageHandler**: Extracted webview message handling to dedicated class for better separation of concerns.
- **Query Builder DRY**: Extracted `buildFilterConditions` helper to eliminate duplicated filter logic between SELECT and COUNT queries.
- **Undo/Redo Refactor**: Extracted undo operations into private methods (`undoCellUpdate`, `undoRowInsert`, etc.) for better readability.
- **BlobInspector Cleanup**: Removed unused `hostBridge` constructor parameter, using `backendApi` consistently.
- **Worker Endpoint Cleanup**: Removed redundant operations proxy object from worker endpoint initialization.
- **Type Definitions**: Added `WasmPreparedStatement` interface replacing `any` types for sql.js statements.

### Testing

- **New Test Suites**: Added comprehensive tests for:
- `ModificationTracker` serialization/deserialization
- `cancelTokenToAbortSignal` utility
- `WebviewCollection` management
- `getUriParts` URI parsing
- `WasmDatabaseEngine.updateCellBatch` batch operations
- `WasmDatabaseEngine.addColumn` column creation
- `toDatasetAttrs` HTML attribute generation
- `SQLiteFileSystemProvider` read/write operations
- `escapeLikePattern` wildcard escaping
- RPC `Transfer` wrapper handling
- **VS Code Mocks**: Added comprehensive VS Code API mocks for unit testing extension code.
- **Test Configuration**: Added `tsconfig.test.json` for proper test compilation with mock paths.

## 1.2.7

### Bug Fixes
Expand Down
25 changes: 23 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ workerProxy.method(new Transfer(data, [data.buffer]));

### Content Security Policy (CSP)
- **Scripts**: Strict nonce-based policy. No `'unsafe-inline'` allowed.
- **Styles**: `'unsafe-inline'` allowed (currently required for dynamic grid layout/resizing).
- **Styles**: Nonce-based policy for `<style>` tags. Dynamic inline styles are applied via CSSOM (`element.style.prop = ...`) which is permitted by CSP.
- **Isolation**: Webview communicates only via RPC.

### Cross-Site Scripting (XSS) Prevention
Expand Down Expand Up @@ -296,9 +296,30 @@ Settings in `package.json` → `contributes.configuration`:
|---------|---------|-------------|
| `sqliteExplorer.maxFileSize` | 200 | Max file size in MB (0 = unlimited) |
| `sqliteExplorer.maxRows` | 0 | Max rows to display (0 = unlimited) |
| `sqliteExplorer.defaultPageSize` | 1000 | Default page size for pagination |
| `sqliteExplorer.defaultPageSize` | 500 | Default page size for pagination |
| `sqliteExplorer.instantCommit` | "never" | Auto-save strategy (always/never/remote-only) |
| `sqliteExplorer.doubleClickBehavior` | "inline" | Double-click action (inline/modal/vscode) |
| `sqliteExplorer.queryTimeout` | 30000 | Query execution timeout in ms (prevents runaway queries) |
| `sqliteExplorer.maxUndoMemory` | 52428800 | Max undo history memory in bytes (default 50MB) |

## Gotchas

### SQL Security
- **LIKE wildcards**: User input in LIKE queries must use `escapeLikePattern()` with `ESCAPE '\\'` clause
- **SQL types in DDL**: Always validate with `validateSqlType()` to prevent injection via malicious type strings
- **NUL bytes**: Strings containing `\0` must be encoded as hex blobs in exported SQL

### RPC Serialization
- **Uint8Array becomes `{}`**: Never send raw Uint8Array via postMessage - use the marker format
- **Transfer for blobs**: Large binary data should use `Transfer` wrapper for zero-copy

### Testing
- **VS Code mocks required**: Unit tests need `tests/mocks/vscode.ts` imported first
- **JSON patch tests**: Test behavior (merged result) not implementation (SQL function)

### Build
- **WASM not found**: Run `node scripts/build.mjs` if `assets/sqlite3.wasm` is missing
- **Browser vs Node**: Check `import.meta.env.VSCODE_BROWSER_EXT` for environment-specific code

## Extension Identifiers

Expand Down
8 changes: 3 additions & 5 deletions core/ui/modules/blob-inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { updateStatus } from './ui.js';
import { validateRowId } from './utils.js';

export class BlobInspector {
constructor(hostBridge) {
this.hostBridge = hostBridge;
constructor() {
this.currentObjectUrl = null;
this.modal = document.getElementById('blob-inspector-modal');
this.previewContainer = document.getElementById('tab-preview');
Expand Down Expand Up @@ -86,9 +85,8 @@ export class BlobInspector {
this.showFileInput();
} else {
// Native mode: Use VS Code API to select file
// NOTE: Use backendApi instead of this.hostBridge because backendApi has the
// proper serialize/deserialize layer for Uint8Array data. While hostBridge
// also uses RPC, backendApi ensures consistent handling across all callers.
// NOTE: Use backendApi directly because it has the proper serialize/deserialize
// layer for Uint8Array data, ensuring consistent handling across all callers.
const result = await backendApi.selectFile();
if (result) {
// Data should already be a Uint8Array after deserialization
Expand Down
9 changes: 8 additions & 1 deletion core/ui/modules/crud.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,14 @@ async function submitDeleteColumns() {

try {
updateStatus('Deleting columns...');
await backendApi.deleteColumns(state.selectedTable, columnNames);
const result = await backendApi.deleteColumns(state.selectedTable, columnNames);

// If user cancelled the operation (e.g., declined to drop dependent indexes), don't reload
if (result && result.cancelled) {
updateStatus('Delete cancelled');
closeModal('deleteModal');
return;
}

closeModal('deleteModal');
state.selectedColumns.clear();
Expand Down
2 changes: 1 addition & 1 deletion core/ui/modules/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { BlobInspector } from './blob-inspector.js';
let blobInspector;

export function initEdit() {
blobInspector = new BlobInspector(backendApi);
blobInspector = new BlobInspector();

// Cell Preview Modal
document.getElementById('btnCloseCellPreview')?.addEventListener('click', closeCellPreview);
Expand Down
9 changes: 7 additions & 2 deletions core/ui/modules/grid.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,12 @@ export async function loadTableData(showSpinner = true, saveScrollPosition = tru
export function renderDataGrid(savedScrollTop = null, savedScrollLeft = null) {
const headerHeight = 52;
const rowHeight = 26;
const rowNumWidth = 50;

// Calculate row number column width based on the largest row number that will be displayed
// Base width: 50px for up to 2 digits, add ~8px per additional digit
const maxRowNum = state.currentPageIndex * state.rowsPerPage + state.gridData.length;
const digitCount = Math.max(2, String(maxRowNum).length);
const rowNumWidth = 36 + (digitCount * 8); // Base 36px + 8px per digit

const container = document.getElementById('gridContainer');
if (!container) return;
Expand Down Expand Up @@ -439,7 +444,7 @@ export function renderDataGrid(savedScrollTop = null, savedScrollLeft = null) {
background: 'var(--bg-secondary)'
});
rowNumTh.title = 'Click to select all rows';
rowNumTh.innerHTML = '<div class="header-content"><div class="header-top" style="height:100%;justify-content:center">#</div></div>';
rowNumTh.innerHTML = '<div class="header-content"><div class="header-top header-top-center">#</div></div>';
headerTr.appendChild(rowNumTh);

for (const col of orderedColumns) {
Expand Down
2 changes: 1 addition & 1 deletion core/ui/modules/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function showErrorState(message) {
if (container) {
container.innerHTML = `
<div class="empty-view">
<span class="empty-icon codicon codicon-error" style="color: var(--error-color)"></span>
<span class="empty-icon codicon codicon-error error-icon"></span>
<span class="empty-title">Error</span>
<span class="empty-desc">${escapeHtml(message)}</span>
</div>
Expand Down
137 changes: 137 additions & 0 deletions core/ui/viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -1115,3 +1115,140 @@ body {
background-color: var(--hover-bg);
outline-offset: -2px;
}

/* ================================================================
UTILITY & NEW CLASSES (Replaces Inline Styles)
================================================================ */
.header-top-center {
height: 100%;
justify-content: center;
}

.error-icon {
color: var(--error-color);
}

.ml-auto {
margin-left: auto;
}

.mr-6 {
margin-right: 6px;
}

.mt-8 {
margin-top: 8px;
}

.settings-section {
border-top: 1px solid var(--border-color);
flex-shrink: 0;
}

.batch-update-container {
padding: 12px;
}

.batch-update-btn {
width: 100%;
margin-bottom: 12px;
}

.modal-dialog-md {
width: 500px;
}

.modal-dialog-lg {
width: 600px;
}

.export-columns-list {
max-height: 150px;
overflow-y: auto;
border: 1px solid var(--border-color);
padding: 8px;
border-radius: 3px;
background: var(--bg-primary);
}

.flex-spacer {
flex: 1;
}

.readonly-badge {
display: none;
margin-left: 8px;
color: var(--vscode-editorWarning-foreground, #cca700);
}

.blob-inspector-dialog {
min-width: 600px;
height: 600px;
}

.blob-inspector-body {
padding: 0;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}

.blob-inspector-tabs {
display: flex;
border-bottom: 1px solid var(--border-color);
background: var(--bg-tertiary);
}

.tab-btn {
padding: 8px 16px;
border: none;
background: none;
color: var(--text-secondary);
cursor: pointer;
border-bottom: 2px solid transparent;
}

.tab-btn.active {
color: var(--text-primary);
border-bottom-color: var(--accent-color);
}

.blob-info-bar {
padding: 8px 16px;
font-size: 11px;
color: var(--text-secondary);
border-bottom: 1px solid var(--border-color);
}

.blob-preview-pane {
flex: 1;
overflow: auto;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-primary);
}

.blob-hex-pane {
display: none;
flex: 1;
overflow: auto;
}

.hex-dump-textarea {
width: 100%;
height: 100%;
resize: none;
border: none;
padding: 12px;
font-family: monospace;
background: var(--bg-primary);
color: var(--text-primary);
outline: none;
}

.blob-preview-placeholder {
color: var(--text-secondary);
}
Loading