Skip to content

Commit 346df20

Browse files
authored
fix(security): resolve ~14 JavaScript CodeQL alerts (#3164)
fix(security): resolve ~14 JavaScript CodeQL alerts (#3164)
2 parents 25c7afd + 58c1397 commit 346df20

28 files changed

Lines changed: 63 additions & 47 deletions

.mcp/autobot-mcp-server.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -786,13 +786,17 @@ class AutoBotMCPServer {
786786
}
787787
}
788788

789-
// Utility methods
789+
// Utility methods — commands are always hardcoded strings from this server,
790+
// never from user/environment input. Shell is restricted to PROJECT_ROOT.
790791
async executeCommand(command, options = {}) {
792+
if (typeof command !== 'string' || command.length === 0) {
793+
throw new Error('Command must be a non-empty string');
794+
}
791795
try {
792-
const result = execSync(command, {
793-
encoding: 'utf8',
796+
const result = execSync(command, { // codeql-suppress js/shell-command-injection-from-environment -- all callers pass hardcoded command strings; this is a local dev MCP server
797+
encoding: 'utf8',
794798
maxBuffer: 1024 * 1024,
795-
...options
799+
...options
796800
});
797801
return result.toString().trim();
798802
} catch (error) {

autobot-frontend/src/components/chat/ChatInput.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ const getFileIcon = (type: string): string => {
774774
// NOTE: formatFileSize removed - now using shared utility from @/utils/formatHelpers
775775
776776
const generateId = (): string => {
777-
return Date.now().toString(36) + Math.random().toString(36).slice(2)
777+
return crypto.randomUUID()
778778
}
779779
780780
// Real file upload implementation

autobot-frontend/src/plugins/errorHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ class GlobalErrorHandler {
210210
this.notifications = []
211211
}
212212

213-
const id = `error_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
213+
const id = `error_${crypto.randomUUID()}`
214214
const fullNotification: ErrorNotification = {
215215
...notification,
216216
id,

autobot-frontend/src/services/GlobalWebSocketService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ class GlobalWebSocketService {
435435
this.baseDelay * Math.pow(2, this.reconnectAttempts - 1),
436436
this.maxDelay
437437
)
438-
const jitter = Math.random() * 1000
438+
const jitter = crypto.getRandomValues(new Uint16Array(1))[0] % 1000
439439
const delay = backoffDelay + jitter
440440

441441
logger.debug(

autobot-frontend/src/services/LiveEventService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class LiveEventService {
175175
}
176176
this.reconnectAttempts++
177177
const delay = Math.min(
178-
this.baseDelay * Math.pow(2, this.reconnectAttempts - 1) + Math.random() * 1000,
178+
this.baseDelay * Math.pow(2, this.reconnectAttempts - 1) + (crypto.getRandomValues(new Uint16Array(1))[0] % 1000),
179179
this.maxDelay
180180
)
181181
logger.debug(

autobot-frontend/src/stores/useAppStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ export const useAppStore = defineStore('app', () => {
324324
const addSystemNotification = (notification: Omit<SystemNotification, 'id' | 'visible' | 'timestamp'>) => {
325325
const newNotification: SystemNotification = {
326326
...notification,
327-
id: `notification-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
327+
id: `notification-${crypto.randomUUID()}`,
328328
visible: true,
329329
timestamp: Date.now()
330330
}

autobot-frontend/src/types/settings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export const createDefaultCacheConfig = (): CacheConfig => ({
228228

229229
// Helper function to create CacheActivity items with required fields
230230
export const createCacheActivityItem = (data: Partial<CacheActivityItem>): CacheActivityItem => ({
231-
id: data.id || Math.random().toString(36).substr(2, 9),
231+
id: data.id || crypto.randomUUID(),
232232
timestamp: data.timestamp || new Date().toISOString(),
233233
operation: data.operation || 'unknown',
234234
key: data.key || '',

autobot-frontend/src/utils/cacheManagement.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
import { createLogger } from '@/utils/debugUtils'
77
import { fetchWithAuth } from '@/utils/fetchWithAuth'
88

9+
function escapeHtml(str: string): string {
10+
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
11+
}
12+
913
// Create scoped logger for cacheManagement
1014
const logger = createLogger('cacheManagement')
1115

@@ -264,7 +268,7 @@ export function showSubtleUpdateNotification(version: string, buildHash: string,
264268
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
265269
</svg>
266270
<span>
267-
<strong>Update available</strong> - v${version}
271+
<strong>Update available</strong> - v${escapeHtml(version)}
268272
<br>
269273
<span style="font-size: 11px; opacity: 0.9;">
270274
⚠️ Unsaved changes detected. Please save your work first.
@@ -281,7 +285,7 @@ export function showSubtleUpdateNotification(version: string, buildHash: string,
281285
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
282286
</svg>
283287
<span>
284-
<strong>Update available</strong> - v${version}
288+
<strong>Update available</strong> - v${escapeHtml(version)}
285289
<span style="font-size: 11px; opacity: 0.8; margin-left: 8px;">
286290
<button onclick="performSafeReload()" style="background: none; border: 1px solid #059669; color: #059669; border-radius: 3px; padding: 2px 6px; margin-left: 8px; font-size: 10px; cursor: pointer;">Refresh now</button>
287291
<button onclick="this.parentElement.parentElement.parentElement.parentElement.remove()" style="background: none; border: 1px solid #6b7280; color: #6b7280; border-radius: 3px; padding: 2px 6px; margin-left: 4px; font-size: 10px; cursor: pointer;">Dismiss</button>
@@ -379,7 +383,7 @@ export function showCacheUpdateNotification(message: string, type: 'info' | 'war
379383
notification.innerHTML = `
380384
<div style="display: flex; align-items: center; gap: 8px;">
381385
<i class="fas fa-sync-alt" style="animation: spin 1s linear infinite;"></i>
382-
${message}
386+
${escapeHtml(message)}
383387
</div>
384388
`
385389

@@ -483,8 +487,8 @@ export function showSubtleErrorNotification(title: string, message: string, seve
483487
${iconSvg}
484488
</svg>
485489
<div style="grow: 1; min-width: 0;">
486-
<div style="font-weight: 600; margin-bottom: 2px;">${title}</div>
487-
<div style="font-size: 12px; opacity: 0.9; line-height: 1.4;">${message}</div>
490+
<div style="font-weight: 600; margin-bottom: 2px;">${escapeHtml(title)}</div>
491+
<div style="font-size: 12px; opacity: 0.9; line-height: 1.4;">${escapeHtml(message)}</div>
488492
</div>
489493
<button
490494
onclick="this.parentElement.parentElement.remove()"

autobot-frontend/src/utils/formHelpers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* Provides reusable form reset, validation, and state manipulation.
66
*/
77

8+
const _UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
9+
810
/**
911
* Reset all fields in a reactive form object to default values
1012
*
@@ -26,6 +28,7 @@ export function resetFormFields<T extends Record<string, any>>(
2628
defaults: Partial<T> = {}
2729
): void {
2830
;(Object.keys(form) as Array<keyof T>).forEach((key) => {
31+
if (_UNSAFE_KEYS.has(key as string)) return
2932
const defaultValue = defaults[key]
3033

3134
if (defaultValue !== undefined) {
@@ -173,6 +176,7 @@ export function isFormModified<T extends Record<string, any>>(
173176
*/
174177
export function trimFormFields<T extends Record<string, any>>(form: T): void {
175178
;(Object.keys(form) as Array<keyof T>).forEach((key) => {
179+
if (_UNSAFE_KEYS.has(key as string)) return
176180
if (typeof form[key] === 'string') {
177181
form[key] = (form[key] as string).trim() as T[keyof T]
178182
}

autobot-slm-frontend/src/components/CodeSourceModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const error = ref<string | null>(null)
4343
// Minimal axios client for code-source POST (Issue #860)
4444
const api = axios.create({ baseURL: getSlmApiBase(), timeout: 15000 })
4545
api.interceptors.request.use((config) => {
46-
const token = localStorage.getItem('slm_access_token')
46+
const token = sessionStorage.getItem('slm_access_token')
4747
if (token) {
4848
config.headers.Authorization = `Bearer ${token}`
4949
}

0 commit comments

Comments
 (0)