Skip to content

Commit 18b7d89

Browse files
zknprclaude
andcommitted
feat(config): Add configurable settings for query timeout and undo memory
Implements all 4 code review suggestions: 1. Expose query timeout as VS Code setting: - Add `sqliteExplorer.queryTimeout` setting (1s-600s, default 30s) - Pass config to worker via DatabaseInitConfig - Use in WasmDatabaseEngine constructor 2. Expose undo memory limit as VS Code setting: - Add `sqliteExplorer.maxUndoMemory` setting (1MB-512MB, default 50MB) - Add getMaxUndoMemory() helper in databaseModel.ts - Pass to ModificationTracker constructor 3. Document why fetchTableData() bypasses timeout: - Add detailed comment explaining pagination naturally bounds execution time, making timeout unnecessary 4. Remove 'unsafe-inline' from CSP: - Remove unused `inlineStyle` constant from helpers.ts - Update CLAUDE.md to reflect full CSP compliance - Dynamic styles use CSSOM which is CSP-compliant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 39e4e20 commit 18b7d89

7 files changed

Lines changed: 51 additions & 5 deletions

File tree

CHANGELOG.md

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

2831
### Refactoring
2932

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ workerProxy.method(new Transfer(data, [data.buffer]));
141141

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

147147
### Cross-Site Scripting (XSS) Prevention

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,20 @@
141141
],
142142
"default": "native",
143143
"markdownDescription": "Controls how file operations (save/upload) behave in the blob inspector.\n- `native`: Use VS Code file dialogs (default).\n- `web`: Use browser download/upload (for web demo compatibility)."
144+
},
145+
"sqliteExplorer.queryTimeout": {
146+
"type": "number",
147+
"minimum": 1000,
148+
"maximum": 600000,
149+
"default": 30000,
150+
"markdownDescription": "Maximum time in milliseconds for query execution before timeout. Prevents runaway queries from freezing the extension. Set to a higher value for complex queries on large databases."
151+
},
152+
"sqliteExplorer.maxUndoMemory": {
153+
"type": "number",
154+
"minimum": 1048576,
155+
"maximum": 536870912,
156+
"default": 52428800,
157+
"markdownDescription": "Maximum memory in bytes for undo/redo history. When exceeded, oldest entries are discarded. Default is 50MB (52428800 bytes)."
144158
}
145159
}
146160
},

src/core/sqlite-db.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,11 @@ class WasmDatabaseEngine implements DatabaseOperations {
623623

624624
/**
625625
* Fetch table data using options.
626+
*
627+
* NOTE: This method intentionally bypasses the query timeout mechanism.
628+
* Unlike raw executeQuery(), fetchTableData() always includes pagination
629+
* (LIMIT/OFFSET) which naturally bounds the result size and execution time.
630+
* The query builder enforces these limits, making timeout unnecessary here.
626631
*/
627632
async fetchTableData(table: string, options: TableQueryOptions): Promise<QueryResultSet> {
628633
const { sql, params } = buildSelectQuery(table, options);
@@ -874,7 +879,7 @@ export async function createDatabaseEngine(
874879
wasmInstance = new SqlJsModule.Database();
875880
}
876881

877-
const engine = new WasmDatabaseEngine(wasmInstance);
882+
const engine = new WasmDatabaseEngine(wasmInstance, config.queryTimeout);
878883

879884
return {
880885
operations: engine,

src/databaseModel.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ export const SupportsWriteMode = IsLocalMode || IsRemoteWorkspaceMode;
5858
/** Maximum modifications to track */
5959
const MODIFICATION_LIMIT = 100;
6060

61+
/** Default maximum memory for undo history (50MB) */
62+
const DEFAULT_MAX_UNDO_MEMORY = 50 * 1024 * 1024;
63+
6164
/**
6265
* Get auto-commit setting from configuration.
6366
*/
@@ -67,6 +70,14 @@ export function isAutoCommitEnabled(): boolean {
6770
return setting === 'always' || (setting === 'remote-only' && IsRemoteWorkspaceMode);
6871
}
6972

73+
/**
74+
* Get maximum undo memory from configuration.
75+
*/
76+
function getMaxUndoMemory(): number {
77+
const config = vsc.workspace.getConfiguration(ConfigurationSection);
78+
return config.get<number>('maxUndoMemory', DEFAULT_MAX_UNDO_MEMORY);
79+
}
80+
7081
// ============================================================================
7182
// Document Class
7283
// ============================================================================
@@ -192,7 +203,7 @@ export class DatabaseDocument extends Disposable implements vsc.CustomDocument {
192203
private readonly reporter?: TelemetryReporter
193204
) {
194205
super();
195-
this.#modificationTracker = tracker ?? new ModificationTracker<DocumentModification>(MODIFICATION_LIMIT);
206+
this.#modificationTracker = tracker ?? new ModificationTracker<DocumentModification>(MODIFICATION_LIMIT, getMaxUndoMemory());
196207
this.#hostBridge = new HostBridge(viewerProvider, this);
197208
this.#documentKey = generateDatabaseDocumentKey(this.uri);
198209
this.#documentKey.then(key => DocumentRegistry.set(key, this));

src/helpers.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ export const cspUtil = {
129129
none: "'none'",
130130
data: 'data:',
131131
blob: 'blob:',
132-
inlineStyle: "'unsafe-inline'",
133132
unsafeEval: "'unsafe-eval'",
134133
wasmUnsafeEval: "'wasm-unsafe-eval'",
135134

src/workerFactory.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ export function getMaximumFileSizeBytes(): number {
6565
return sizeMB * (2 ** 20);
6666
}
6767

68+
/** Default query timeout in milliseconds (30 seconds) */
69+
const DEFAULT_QUERY_TIMEOUT_MS = 30000;
70+
71+
/**
72+
* Retrieve query timeout from user configuration.
73+
*
74+
* @returns Query timeout in milliseconds
75+
*/
76+
export function getQueryTimeout(): number {
77+
const config = vsc.workspace.getConfiguration(ConfigurationSection);
78+
return config.get<number>('queryTimeout', DEFAULT_QUERY_TIMEOUT_MS);
79+
}
80+
6881
// ============================================================================
6982
// Worker Interface Types
7083
// ============================================================================
@@ -280,7 +293,8 @@ async function createWasmDatabaseConnection(
280293
maxSize: getMaximumFileSizeBytes(),
281294
resourceMap: {},
282295
wasmBinary: wasmContent,
283-
readOnlyMode: forceReadOnly ?? false
296+
readOnlyMode: forceReadOnly ?? false,
297+
queryTimeout: getQueryTimeout()
284298
};
285299

286300
// Initialize database in worker

0 commit comments

Comments
 (0)