Skip to content

Commit f183201

Browse files
authored
Merge pull request #968 from Chris0Jeky/fix/fe-24-console-error-sanitization
FE-24: Sanitize console.error API detail exposure in production
2 parents cec2424 + b2b6b78 commit f183201

19 files changed

Lines changed: 277 additions & 45 deletions

frontend/taskdeck-web/src/api/http.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createRequestId } from '../utils/requestId'
44
import { isAuthRoutePath } from '../utils/navigation'
55
import { isDemoMode } from '../utils/demoMode'
66
import * as tokenStorage from '../utils/tokenStorage'
7+
import { logError, logWarn } from '../utils/errorReporting'
78
import {
89
MAX_RETRIES,
910
computeRetryDelay,
@@ -51,7 +52,16 @@ http.interceptors.response.use(
5152
(response) => response,
5253
(error) => {
5354
if (error.response) {
54-
console.error('API Error:', error.response.data)
55+
// Log only safe, non-sensitive details -- never the full error object,
56+
// which includes error.config.headers (Authorization: Bearer ...).
57+
const safeDetails = {
58+
status: error.response?.status,
59+
statusText: error.response?.statusText,
60+
data: error.response?.data,
61+
url: error.config?.url,
62+
method: error.config?.method,
63+
}
64+
logError('API Error:', safeDetails)
5565

5666
// Handle 401 - clear session and redirect to login (skip in demo mode).
5767
// Callers can set `skipAuth401` on the request config to suppress this
@@ -66,9 +76,9 @@ http.interceptors.response.use(
6676
}
6777
}
6878
} else if (error.request) {
69-
console.error('Network Error:', error.message)
79+
logError('Network Error:', { message: error.message })
7080
} else {
71-
console.error('Error:', error.message)
81+
logError('Error:', { message: error.message })
7282
}
7383
return Promise.reject(error)
7484
}
@@ -108,7 +118,7 @@ http.interceptors.response.use(
108118
const delay = computeRetryDelay(error, attempt)
109119
if (import.meta.env.DEV) {
110120
const status = error.response?.status ?? 'network'
111-
console.warn(
121+
logWarn(
112122
`[http] retry ${attempt}/${MAX_RETRIES} for ${config.method?.toUpperCase()} ${config.url} ` +
113123
`after ${delay}ms (status=${status})`,
114124
)

frontend/taskdeck-web/src/components/ErrorBoundary.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
*/
1313
import { onErrorCaptured, ref, watch } from 'vue'
1414
import { useRoute, useRouter } from 'vue-router'
15-
import { reportToSentry } from '../utils/errorReporting'
15+
import { reportToSentry, logError } from '../utils/errorReporting'
1616
1717
const props = withDefaults(
1818
defineProps<{
@@ -102,7 +102,7 @@ onErrorCaptured((err, _instance, info) => {
102102
crashInfo.value = info
103103
104104
// Always log so errors are not silently swallowed.
105-
console.error('[ErrorBoundary] caught error', err, info)
105+
logError('[ErrorBoundary] caught error', err, info)
106106
107107
// Forward to Sentry via the centralized utility so the lifecycle `info`
108108
// string is preserved and reporting behavior stays consistent across the

frontend/taskdeck-web/src/components/board/BoardSettingsModal.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
44
import { useBoardStore } from '../../store/boardStore'
55
import { useEscapeToClose } from '../../composables/useEscapeToClose'
66
import type { Board } from '../../types/board'
7+
import { logError } from '../../utils/errorReporting'
78
89
const props = defineProps<{
910
board: Board
@@ -67,7 +68,7 @@ async function handleSave() {
6768
emit('updated')
6869
emit('close')
6970
} catch (error) {
70-
console.error('Failed to update board:', error)
71+
logError('Failed to update board:', error)
7172
}
7273
}
7374
@@ -107,7 +108,7 @@ async function handleLifecycleTransition() {
107108
emit('close')
108109
}
109110
} catch (error) {
110-
console.error('Failed to update board lifecycle state:', error)
111+
logError('Failed to update board lifecycle state:', error)
111112
} finally {
112113
lifecycleActionInProgress.value = false
113114
}

frontend/taskdeck-web/src/components/board/ColumnEditModal.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ref, watch } from 'vue'
33
import { useBoardStore } from '../../store/boardStore'
44
import { useEscapeToClose } from '../../composables/useEscapeToClose'
55
import type { Column } from '../../types/board'
6+
import { logError } from '../../utils/errorReporting'
67
78
const props = defineProps<{
89
column: Column
@@ -50,7 +51,7 @@ async function handleSave() {
5051
emit('updated')
5152
emit('close')
5253
} catch (error) {
53-
console.error('Failed to update column:', error)
54+
logError('Failed to update column:', error)
5455
}
5556
}
5657
@@ -67,7 +68,7 @@ async function handleDelete() {
6768
emit('updated')
6869
emit('close')
6970
} catch (error) {
70-
console.error('Failed to delete column:', error)
71+
logError('Failed to delete column:', error)
7172
}
7273
}
7374

frontend/taskdeck-web/src/components/board/ColumnLane.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import CardItem from './CardItem.vue'
77
import CardModal from './CardModal.vue'
88
import ColumnEditModal from './ColumnEditModal.vue'
99
import type { Column, Card, Label } from '../../types/board'
10+
import { logError } from '../../utils/errorReporting'
1011
1112
const props = defineProps<{
1213
column: Column
@@ -66,7 +67,7 @@ async function createCard() {
6667
} catch (error) {
6768
const { message } = getErrorDisplay(error, 'Failed to create card')
6869
toast.error(message)
69-
console.error('Failed to create card:', error)
70+
logError('Failed to create card:', error)
7071
}
7172
}
7273
@@ -120,7 +121,7 @@ async function handleDrop(event: DragEvent) {
120121
targetPosition
121122
)
122123
} catch (error) {
123-
console.error('Failed to move card:', error)
124+
logError('Failed to move card:', error)
124125
}
125126
}
126127
@@ -151,7 +152,7 @@ async function handleCardDrop(targetCard: Card, event: DragEvent) {
151152
targetPosition
152153
)
153154
} catch (error) {
154-
console.error('Failed to move card:', error)
155+
logError('Failed to move card:', error)
155156
}
156157
}
157158
@@ -173,7 +174,7 @@ async function handleCardMoveTo(card: Card, targetColumnId: string) {
173174
el.focus()
174175
}
175176
} catch (error) {
176-
console.error('Failed to move card:', error)
177+
logError('Failed to move card:', error)
177178
}
178179
}
179180

frontend/taskdeck-web/src/components/board/LabelManagerModal.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
33
import { useBoardStore } from '../../store/boardStore'
44
import { useEscapeToClose } from '../../composables/useEscapeToClose'
55
import type { Label } from '../../types/board'
6+
import { logError } from '../../utils/errorReporting'
67
78
const props = defineProps<{
89
isOpen: boolean
@@ -87,7 +88,7 @@ async function handleSaveLabel() {
8788
cancelForm()
8889
emit('updated')
8990
} catch (error) {
90-
console.error('Failed to save label:', error)
91+
logError('Failed to save label:', error)
9192
}
9293
}
9394
@@ -98,7 +99,7 @@ async function handleDeleteLabel(label: Label) {
9899
await boardStore.deleteLabel(props.boardId, label.id)
99100
emit('updated')
100101
} catch (error) {
101-
console.error('Failed to delete label:', error)
102+
logError('Failed to delete label:', error)
102103
}
103104
}
104105

frontend/taskdeck-web/src/composables/useAnalyticsScript.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { watch, onUnmounted } from 'vue'
22
import { useTelemetryStore } from '../store/telemetryStore'
3+
import { logWarn } from '../utils/errorReporting'
34

45
const SCRIPT_ID = 'taskdeck-analytics-script'
56

@@ -54,19 +55,19 @@ export function useAnalyticsScript() {
5455

5556
// Only allow HTTPS URLs to prevent javascript:, data:, or blob: injection
5657
if (!isValidScriptUrl(config.scriptUrl)) {
57-
console.warn('[Taskdeck] Analytics script URL rejected: must be HTTPS', config.scriptUrl)
58+
logWarn('[Taskdeck] Analytics script URL rejected: must be HTTPS', config.scriptUrl)
5859
return
5960
}
6061

6162
// Validate provider to prevent arbitrary attribute injection
6263
if (!isValidProvider(config.provider)) {
63-
console.warn('[Taskdeck] Analytics provider rejected: unsupported provider', config.provider)
64+
logWarn('[Taskdeck] Analytics provider rejected: unsupported provider', config.provider)
6465
return
6566
}
6667

6768
// Validate siteId to prevent injection via data attributes
6869
if (!isValidSiteId(config.siteId)) {
69-
console.warn('[Taskdeck] Analytics siteId rejected: invalid format', config.siteId)
70+
logWarn('[Taskdeck] Analytics siteId rejected: invalid format', config.siteId)
7071
return
7172
}
7273

@@ -129,17 +130,17 @@ export function initAnalyticsScriptWatcher() {
129130
if (!config) return
130131

131132
if (!isValidScriptUrl(config.scriptUrl)) {
132-
console.warn('[Taskdeck] Analytics script URL rejected: must be HTTPS', config.scriptUrl)
133+
logWarn('[Taskdeck] Analytics script URL rejected: must be HTTPS', config.scriptUrl)
133134
return
134135
}
135136

136137
if (!isValidProvider(config.provider)) {
137-
console.warn('[Taskdeck] Analytics provider rejected: unsupported provider', config.provider)
138+
logWarn('[Taskdeck] Analytics provider rejected: unsupported provider', config.provider)
138139
return
139140
}
140141

141142
if (!isValidSiteId(config.siteId)) {
142-
console.warn('[Taskdeck] Analytics siteId rejected: invalid format', config.siteId)
143+
logWarn('[Taskdeck] Analytics siteId rejected: invalid format', config.siteId)
143144
return
144145
}
145146

frontend/taskdeck-web/src/composables/useBoardDragDrop.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ref, type ComputedRef } from 'vue'
22
import type { Column, Card } from '../types/board'
33
import { useBoardStore } from '../store/boardStore'
4+
import { logError } from '../utils/errorReporting'
45

56
/**
67
* Composable encapsulating all drag-and-drop state and handlers for the board view.
@@ -75,7 +76,7 @@ export function useBoardDragDrop(boardId: () => string, sortedColumns: ComputedR
7576
const columnIds = reordered.map((col) => col.id)
7677
await boardStore.reorderColumns(boardId(), columnIds)
7778
} catch (error) {
78-
console.error('Failed to reorder columns:', error)
79+
logError('Failed to reorder columns:', error)
7980
}
8081
}
8182

frontend/taskdeck-web/src/composables/useBoardRealtime.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '@microsoft/signalr'
88
import type { BoardPresenceSnapshot, BoardRealtimeEvent } from '../types/realtime'
99
import { getToken } from '../utils/tokenStorage'
10+
import { logWarn } from '../utils/errorReporting'
1011

1112
const BOARD_MUTATION_EVENT = 'boardMutation'
1213
const BOARD_PRESENCE_EVENT = 'boardPresence'
@@ -159,7 +160,7 @@ export function createBoardRealtimeController(
159160
try {
160161
await hubConnection.start()
161162
} catch (error) {
162-
console.warn('SignalR board realtime unavailable, using polling fallback.', error)
163+
logWarn('SignalR board realtime unavailable, using polling fallback.', error)
163164
startFallbackPolling(boardId)
164165
subscribedBoardId = boardId
165166
return

frontend/taskdeck-web/src/composables/useCardModal.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useBoardStore } from '../store/boardStore'
33
import { useSessionStore } from '../store/sessionStore'
44
import type { Card, CardCaptureProvenance, Label } from '../types/board'
55
import type { CardComment } from '../types/comments'
6+
import { logError } from '../utils/errorReporting'
67

78
export interface UseCardModalOptions {
89
getCard: () => Card
@@ -154,7 +155,7 @@ export function useCardModal(options: UseCardModalOptions) {
154155
options.onUpdated()
155156
options.onClose()
156157
} catch (error) {
157-
console.error('Failed to update card:', error)
158+
logError('Failed to update card:', error)
158159
}
159160
}
160161

@@ -176,7 +177,7 @@ export function useCardModal(options: UseCardModalOptions) {
176177
options.onUpdated()
177178
options.onClose()
178179
} catch (error) {
179-
console.error('Failed to delete card:', error)
180+
logError('Failed to delete card:', error)
180181
} finally {
181182
isDeleting.value = false
182183
}
@@ -217,7 +218,7 @@ export function useCardModal(options: UseCardModalOptions) {
217218
newCommentContent.value = ''
218219
}
219220
} catch (error) {
220-
console.error('Failed to add comment:', error)
221+
logError('Failed to add comment:', error)
221222
}
222223
}
223224

@@ -245,7 +246,7 @@ export function useCardModal(options: UseCardModalOptions) {
245246
await boardStore.updateCardComment(card.value.boardId, card.value.id, commentId, { content })
246247
handleCancelEditComment()
247248
} catch (error) {
248-
console.error('Failed to update comment:', error)
249+
logError('Failed to update comment:', error)
249250
}
250251
}
251252

@@ -261,7 +262,7 @@ export function useCardModal(options: UseCardModalOptions) {
261262
try {
262263
await boardStore.deleteCardComment(card.value.boardId, card.value.id, comment.id)
263264
} catch (error) {
264-
console.error('Failed to delete comment:', error)
265+
logError('Failed to delete comment:', error)
265266
}
266267
}
267268

0 commit comments

Comments
 (0)