Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 8 additions & 4 deletions .mcp/autobot-mcp-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -786,13 +786,17 @@ class AutoBotMCPServer {
}
}

// Utility methods
// Utility methods — commands are always hardcoded strings from this server,
// never from user/environment input. Shell is restricted to PROJECT_ROOT.
async executeCommand(command, options = {}) {
if (typeof command !== 'string' || command.length === 0) {
throw new Error('Command must be a non-empty string');
}
try {
const result = execSync(command, {
encoding: 'utf8',
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
encoding: 'utf8',
maxBuffer: 1024 * 1024,
...options
...options
});
return result.toString().trim();
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion autobot-frontend/src/components/chat/ChatInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ const getFileIcon = (type: string): string => {
// NOTE: formatFileSize removed - now using shared utility from @/utils/formatHelpers

const generateId = (): string => {
return Date.now().toString(36) + Math.random().toString(36).slice(2)
return crypto.randomUUID()
}

// Real file upload implementation
Expand Down
2 changes: 1 addition & 1 deletion autobot-frontend/src/plugins/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ class GlobalErrorHandler {
this.notifications = []
}

const id = `error_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
const id = `error_${crypto.randomUUID()}`
const fullNotification: ErrorNotification = {
...notification,
id,
Expand Down
2 changes: 1 addition & 1 deletion autobot-frontend/src/services/GlobalWebSocketService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ class GlobalWebSocketService {
this.baseDelay * Math.pow(2, this.reconnectAttempts - 1),
this.maxDelay
)
const jitter = Math.random() * 1000
const jitter = crypto.getRandomValues(new Uint16Array(1))[0] % 1000
const delay = backoffDelay + jitter

logger.debug(
Expand Down
2 changes: 1 addition & 1 deletion autobot-frontend/src/services/LiveEventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class LiveEventService {
}
this.reconnectAttempts++
const delay = Math.min(
this.baseDelay * Math.pow(2, this.reconnectAttempts - 1) + Math.random() * 1000,
this.baseDelay * Math.pow(2, this.reconnectAttempts - 1) + (crypto.getRandomValues(new Uint16Array(1))[0] % 1000),
this.maxDelay
)
logger.debug(
Expand Down
2 changes: 1 addition & 1 deletion autobot-frontend/src/stores/useAppStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ export const useAppStore = defineStore('app', () => {
const addSystemNotification = (notification: Omit<SystemNotification, 'id' | 'visible' | 'timestamp'>) => {
const newNotification: SystemNotification = {
...notification,
id: `notification-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
id: `notification-${crypto.randomUUID()}`,
visible: true,
timestamp: Date.now()
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-frontend/src/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export const createDefaultCacheConfig = (): CacheConfig => ({

// Helper function to create CacheActivity items with required fields
export const createCacheActivityItem = (data: Partial<CacheActivityItem>): CacheActivityItem => ({
id: data.id || Math.random().toString(36).substr(2, 9),
id: data.id || crypto.randomUUID(),
timestamp: data.timestamp || new Date().toISOString(),
operation: data.operation || 'unknown',
key: data.key || '',
Expand Down
14 changes: 9 additions & 5 deletions autobot-frontend/src/utils/cacheManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import { createLogger } from '@/utils/debugUtils'
import { fetchWithAuth } from '@/utils/fetchWithAuth'

function escapeHtml(str: string): string {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}

// Create scoped logger for cacheManagement
const logger = createLogger('cacheManagement')

Expand Down Expand Up @@ -264,7 +268,7 @@ export function showSubtleUpdateNotification(version: string, buildHash: string,
<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" />
</svg>
<span>
<strong>Update available</strong> - v${version}
<strong>Update available</strong> - v${escapeHtml(version)}
<br>
<span style="font-size: 11px; opacity: 0.9;">
⚠️ Unsaved changes detected. Please save your work first.
Expand All @@ -281,7 +285,7 @@ export function showSubtleUpdateNotification(version: string, buildHash: string,
<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" />
</svg>
<span>
<strong>Update available</strong> - v${version}
<strong>Update available</strong> - v${escapeHtml(version)}
<span style="font-size: 11px; opacity: 0.8; margin-left: 8px;">
<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>
<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>
Expand Down Expand Up @@ -379,7 +383,7 @@ export function showCacheUpdateNotification(message: string, type: 'info' | 'war
notification.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px;">
<i class="fas fa-sync-alt" style="animation: spin 1s linear infinite;"></i>
${message}
${escapeHtml(message)}
</div>
`

Expand Down Expand Up @@ -483,8 +487,8 @@ export function showSubtleErrorNotification(title: string, message: string, seve
${iconSvg}
</svg>
<div style="grow: 1; min-width: 0;">
<div style="font-weight: 600; margin-bottom: 2px;">${title}</div>
<div style="font-size: 12px; opacity: 0.9; line-height: 1.4;">${message}</div>
<div style="font-weight: 600; margin-bottom: 2px;">${escapeHtml(title)}</div>
<div style="font-size: 12px; opacity: 0.9; line-height: 1.4;">${escapeHtml(message)}</div>
</div>
<button
onclick="this.parentElement.parentElement.remove()"
Expand Down
4 changes: 4 additions & 0 deletions autobot-frontend/src/utils/formHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* Provides reusable form reset, validation, and state manipulation.
*/

const _UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype'])

/**
* Reset all fields in a reactive form object to default values
*
Expand All @@ -26,6 +28,7 @@ export function resetFormFields<T extends Record<string, any>>(
defaults: Partial<T> = {}
): void {
;(Object.keys(form) as Array<keyof T>).forEach((key) => {
if (_UNSAFE_KEYS.has(key as string)) return
const defaultValue = defaults[key]

if (defaultValue !== undefined) {
Expand Down Expand Up @@ -173,6 +176,7 @@ export function isFormModified<T extends Record<string, any>>(
*/
export function trimFormFields<T extends Record<string, any>>(form: T): void {
;(Object.keys(form) as Array<keyof T>).forEach((key) => {
if (_UNSAFE_KEYS.has(key as string)) return
if (typeof form[key] === 'string') {
form[key] = (form[key] as string).trim() as T[keyof T]
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/components/CodeSourceModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const error = ref<string | null>(null)
// Minimal axios client for code-source POST (Issue #860)
const api = axios.create({ baseURL: getSlmApiBase(), timeout: 15000 })
api.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/components/DeploymentWizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async function fetchRoles(): Promise<void> {
try {
const response = await fetch('/api/deployments/roles', {
headers: {
Authorization: `Bearer ${localStorage.getItem('slm_access_token')}`,
Authorization: `Bearer ${sessionStorage.getItem('slm_access_token')}`,
},
})
if (response.ok) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ async function loadPlaybooks(): Promise<void> {
try {
const response = await fetch(`${getSlmApiBase()}/infrastructure/playbooks`, {
headers: {
Authorization: `Bearer ${localStorage.getItem('slm_access_token')}`,
Authorization: `Bearer ${sessionStorage.getItem('slm_access_token')}`,
},
})
if (response.ok) {
Expand All @@ -159,7 +159,7 @@ async function execute(): Promise<void> {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('slm_access_token')}`,
Authorization: `Bearer ${sessionStorage.getItem('slm_access_token')}`,
},
body: JSON.stringify({
playbook_id: selectedPlaybookId.value,
Expand Down Expand Up @@ -196,7 +196,7 @@ async function pollExecutionStatus(executionId: string): Promise<void> {
try {
const response = await fetch(`${getSlmApiBase()}/infrastructure/executions/${executionId}`, {
headers: {
Authorization: `Bearer ${localStorage.getItem('slm_access_token')}`,
Authorization: `Bearer ${sessionStorage.getItem('slm_access_token')}`,
},
})

Expand Down Expand Up @@ -224,7 +224,7 @@ async function cancelExecution(): Promise<void> {
{
method: 'POST',
headers: {
Authorization: `Bearer ${localStorage.getItem('slm_access_token')}`,
Authorization: `Bearer ${sessionStorage.getItem('slm_access_token')}`,
},
}
)
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/composables/useCodeSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function useCodeSource() {
// Use relative path for same-origin SLM backend API (Issue #860)
const api = axios.create({ baseURL: getSlmApiBase(), timeout: 15000 })
api.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/composables/useCodeSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export function useCodeSync() {

// Add auth token to all requests
client.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const API_BASE = ''
function makeClient() {
const client = axios.create({ baseURL: API_BASE })
client.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/composables/useOrchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function useOrchestration() {

// Add auth token to all requests
client.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function useOrchestrationManagement() {

// Add auth token to all requests
client.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/composables/useRoles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export function useRoles() {
// Create axios instance with auth
const client = axios.create({ baseURL: API_BASE })
client.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/composables/useSlmApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export function useSlmApi() {

// Add auth token to all requests
client.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/composables/useSystemUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function useSystemUpdates() {
})

client.interceptors.request.use((config) => {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ router.beforeEach(async (to, _from) => {
if (!wizardCheckDone && to.name !== 'setup') {
wizardCheckDone = true
try {
const token = localStorage.getItem('slm_access_token')
const token = sessionStorage.getItem('slm_access_token')
const { data } = await axios.get('/api/setup/status', {
headers: { Authorization: `Bearer ${token}` },
})
Expand Down
20 changes: 12 additions & 8 deletions autobot-slm-frontend/src/stores/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ const USER_KEY = 'slm_user'
export const useAuthStore = defineStore('auth', () => {
const router = useRouter()

const token = ref<string | null>(localStorage.getItem(TOKEN_KEY))
// Session-scoped storage: tokens are cleared when the browser tab closes
const token = ref<string | null>(sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY))
const user = ref<User | null>(
localStorage.getItem(USER_KEY)
? JSON.parse(localStorage.getItem(USER_KEY)!)
: null
(() => {
const raw = sessionStorage.getItem(USER_KEY) || localStorage.getItem(USER_KEY)
return raw ? JSON.parse(raw) : null
})()
)
const loading = ref(false)
const error = ref<string | null>(null)
Expand Down Expand Up @@ -87,7 +89,7 @@ export const useAuthStore = defineStore('auth', () => {
}

token.value = data.access_token!
localStorage.setItem(TOKEN_KEY, data.access_token!)
sessionStorage.setItem(TOKEN_KEY, data.access_token!)

await fetchCurrentUser()
return true
Expand All @@ -114,7 +116,7 @@ export const useAuthStore = defineStore('auth', () => {
}
const data: TokenResponse = await response.json()
token.value = data.access_token
localStorage.setItem(TOKEN_KEY, data.access_token)
sessionStorage.setItem(TOKEN_KEY, data.access_token)
mfaPending.value = false
mfaTempToken.value = ''
await fetchCurrentUser()
Expand Down Expand Up @@ -152,7 +154,7 @@ export const useAuthStore = defineStore('auth', () => {
username: data.username,
isAdmin: data.is_admin,
}
localStorage.setItem(USER_KEY, JSON.stringify(user.value))
sessionStorage.setItem(USER_KEY, JSON.stringify(user.value))
} catch {
logout()
}
Expand All @@ -175,7 +177,7 @@ export const useAuthStore = defineStore('auth', () => {

const data: TokenResponse = await response.json()
token.value = data.access_token
localStorage.setItem(TOKEN_KEY, data.access_token)
sessionStorage.setItem(TOKEN_KEY, data.access_token)
return true
} catch {
logout()
Expand All @@ -186,6 +188,8 @@ export const useAuthStore = defineStore('auth', () => {
function logout(): void {
token.value = null
user.value = null
sessionStorage.removeItem(TOKEN_KEY)
sessionStorage.removeItem(USER_KEY)
localStorage.removeItem(TOKEN_KEY)
localStorage.removeItem(USER_KEY)
router.push('/login')
Expand Down
8 changes: 4 additions & 4 deletions autobot-slm-frontend/src/views/DeploymentsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ function getNodeHostname(nodeId: string): string {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="getStatusIcon(deployment.status)" />
</svg>
<span :class="['px-2 py-1 text-xs font-medium rounded-full', getStatusClass(deployment.status)]">
{{ deployment.status.replace('_', ' ') }}
{{ deployment.status.replaceAll('_', ' ') }}
</span>
</div>
</td>
Expand Down Expand Up @@ -1031,7 +1031,7 @@ function getNodeHostname(nodeId: string): string {
<tr v-for="deployment in bgDeployments" :key="deployment.bg_deployment_id" class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap">
<span :class="['px-2 py-1 text-xs font-medium rounded-full', getStatusClass(deployment.status)]">
{{ deployment.status.replace('_', ' ') }}
{{ deployment.status.replaceAll('_', ' ') }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
Expand Down Expand Up @@ -1515,7 +1515,7 @@ function getNodeHostname(nodeId: string): string {
<div class="flex items-center gap-3">
<h3 class="text-lg font-semibold text-gray-900">Deployment Details</h3>
<span :class="['px-2 py-1 text-xs font-medium rounded-full', getStatusClass(selectedDeployment.status)]">
{{ selectedDeployment.status.replace('_', ' ') }}
{{ selectedDeployment.status.replaceAll('_', ' ') }}
</span>
</div>
<button @click="closeDetails" class="text-gray-400 hover:text-gray-600">
Expand Down Expand Up @@ -1619,7 +1619,7 @@ function getNodeHostname(nodeId: string): string {
<div class="flex items-center gap-3">
<h3 class="text-lg font-semibold text-gray-900">Blue-Green Deployment Details</h3>
<span :class="['px-2 py-1 text-xs font-medium rounded-full', getStatusClass(selectedBgDeployment.status)]">
{{ selectedBgDeployment.status.replace('_', ' ') }}
{{ selectedBgDeployment.status.replaceAll('_', ' ') }}
</span>
</div>
<button @click="closeBgDetails" class="text-gray-400 hover:text-gray-600">
Expand Down
2 changes: 1 addition & 1 deletion autobot-slm-frontend/src/views/InfrastructureView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async function loadPlaybooks(): Promise<void> {
try {
const response = await fetch('/api/infrastructure/playbooks', {
headers: {
Authorization: `Bearer ${localStorage.getItem('slm_access_token')}`,
Authorization: `Bearer ${sessionStorage.getItem('slm_access_token')}`,
},
})
if (response.ok) {
Expand Down
Loading
Loading