From c26a1d727a9ac62dd9370e13b19455dd58ba66da Mon Sep 17 00:00:00 2001 From: 777genius Date: Fri, 22 May 2026 01:24:23 +0300 Subject: [PATCH] fix(lint): simplify release validation patterns --- .../services/team/AgentTeamsMcpHttpServer.ts | 3 +- src/renderer/utils/memberLaunchDiagnostics.ts | 91 ++++++++++++++++--- src/shared/utils/sentryConfig.ts | 46 ++++++++-- 3 files changed, 117 insertions(+), 23 deletions(-) diff --git a/src/main/services/team/AgentTeamsMcpHttpServer.ts b/src/main/services/team/AgentTeamsMcpHttpServer.ts index b4c179f1..da4802cc 100644 --- a/src/main/services/team/AgentTeamsMcpHttpServer.ts +++ b/src/main/services/team/AgentTeamsMcpHttpServer.ts @@ -1341,8 +1341,7 @@ export class AgentTeamsMcpHttpServer { ): Promise { const ownedPids = [rootPid]; const visited = new Set(ownedPids); - for (let index = 0; index < ownedPids.length; index += 1) { - const parentPid = ownedPids[index]; + for (const parentPid of ownedPids) { for (const row of rows) { if (row.ppid !== parentPid || visited.has(row.pid)) { continue; diff --git a/src/renderer/utils/memberLaunchDiagnostics.ts b/src/renderer/utils/memberLaunchDiagnostics.ts index bf8987b5..fe316b72 100644 --- a/src/renderer/utils/memberLaunchDiagnostics.ts +++ b/src/renderer/utils/memberLaunchDiagnostics.ts @@ -75,15 +75,26 @@ const MAX_DIAGNOSTIC_ITEMS = 20; const MAX_PERMISSION_REQUEST_IDS = 10; const SECRET_FLAG_PATTERN = /(--(?:api-key|token|password|secret|authorization|auth-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi; -const SECRET_VALUE_PATTERN = - /\b(sk-[A-Za-z0-9._~+/=-]{12,}|[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}|[A-Z0-9_]*(?:API_KEY|AUTH_TOKEN|TOKEN|SECRET|PASSWORD|AUTHORIZATION)[A-Z0-9_]*\s*=\s*("[^"]*"|'[^']*'|\S+))\b/gi; -const OPENCODE_SESSION_REFRESH_REASON_PATTERN = - /\b(?:resolved_behavior_changed|opencode_app_mcp_transport_changed):[-a-z0-9._~/=]+->[-a-z0-9._~/=]+/i; +const SECRET_VALUE_PATTERNS: [RegExp, string][] = [ + [/\bsk-\S{12,}\b/gi, '[redacted]'], + [/\b[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g, '[redacted]'], +]; +const SECRET_ENV_KEY_PARTS = [ + 'API_KEY', + 'AUTH_TOKEN', + 'TOKEN', + 'SECRET', + 'PASSWORD', + 'AUTHORIZATION', +]; +const OPENCODE_SESSION_REFRESH_REASON_MARKERS = [ + 'resolved_behavior_changed', + 'opencode_app_mcp_transport_changed', +] as const; +const OPENCODE_SESSION_REFRESH_REASON_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789._~/=->'; const OPENCODE_SESSION_REFRESH_FAILURE_PATTERN = // eslint-disable-next-line sonarjs/regex-complexity -- Keyword taxonomy is kept literal to preserve diagnostic behavior. /(?:^|[_\s:;.\/()-])(?:permission[_\s-]?denied|permission[_\s-]?blocked|access[_\s-]?denied|auth[_\s-]?unavailable|authentication[_\s-]?failed|unauthorized|forbidden|401|403|login[_\s-]?required|not\s+logged\s+in|missing\s+credentials?|invalid\s+credentials?|credentials?[_\s-]?required|credentials?[_\s-]?unavailable|no auth available|authorization|auth(?:entication)?(?:[_\s-]?(?:failed|unavailable))?|invalid api[_\s-]?key|api[_\s-]?key|does not have access|quota|rate[_\s-]?(?:limit|limited)|too many requests|429|model cooldown|cooling down|enospc|no space left|disk is full|capacity exceeded|quota exhausted|usage exceeded|free usage exceeded|key limit exceeded|total limit|insufficient credits|subscribe to go|error|failed|failure|timeout|timed\s+out|network|connection|unable\s+to\s+connect|connect\s+failed|econn[a-z_]*|enotfound|fetch[_\s-]?failed|connection[_\s-]?(?:refused|reset)|aborted|cancel(?:ed|led)|interrupted|service[_\s-]?unavailable|temporarily\s+unavailable|overloaded|visible[_\s-]?reply(?:[_\s-][a-z0-9]+)*|task[_\s-]?refs|relayofmessageid|relay[_\s-]?of[_\s-]?message[_\s-]?id|message[_\s-]?send|non[_\s-]?visible[_\s-]?tool(?:[_\s-][a-z0-9]+)*|protocol[_\s-]?proof)(?=$|[_\s:;.\/(),-])/i; -const OPENCODE_SESSION_REFRESH_ANY_REASON_PATTERN = - /\b(?:resolved_behavior_changed|opencode_app_mcp_transport_changed):[-a-z0-9._~/=]+->[-a-z0-9._~/=]+/gi; const OPENCODE_SESSION_REFRESH_SAFE_MARKER_STATE_PATTERN = /\b(?:not_observed|pending|prompt_not_indexed|responded_tool_call|responded_visible_message|responded_non_visible_tool|responded_plain_text|permission_blocked|tool_error|empty_assistant_turn|prompt_delivered_no_assistant_message|session_stale|session_error|reconcile_failed)\b/g; @@ -110,14 +121,25 @@ function boundedString( ): string | undefined { const trimmed = value?.replace(/\s+/g, ' ').trim(); if (!trimmed) return undefined; - const redacted = trimmed - .replace(SECRET_FLAG_PATTERN, '$1[redacted]') - .replace(SECRET_VALUE_PATTERN, '[redacted]'); + const redacted = redactDiagnosticEnvAssignments( + SECRET_VALUE_PATTERNS.reduce( + (current, [pattern, replacement]) => current.replace(pattern, replacement), + trimmed.replace(SECRET_FLAG_PATTERN, '$1[redacted]') + ) + ); return redacted.length > maxLength ? `${redacted.slice(0, Math.max(0, maxLength - 3))}...` : redacted; } +function redactDiagnosticEnvAssignments(value: string): string { + return value.replace(/\b[A-Z0-9_]+\s*=\s*("[^"]*"|'[^']*'|\S+)/gi, (assignment) => { + const separatorIndex = assignment.indexOf('='); + const key = assignment.slice(0, separatorIndex).trim().toUpperCase(); + return SECRET_ENV_KEY_PARTS.some((part) => key.includes(part)) ? '[redacted]' : assignment; + }); +} + function boundedNumber(value: number | undefined): number | undefined { return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.trunc(value) @@ -181,14 +203,15 @@ function isRecoverableOpenCodeSessionRefreshText(value: string | undefined): boo ) { return true; } - if (!OPENCODE_SESSION_REFRESH_REASON_PATTERN.test(refreshText)) { + const reasonRanges = findOpenCodeSessionRefreshReasonRanges(refreshText); + if (reasonRanges.length === 0) { return false; } const markerText = refreshText; if (hasOpenCodeSessionRefreshFailureConflict(markerText)) { return false; } - const rawRemainder = markerText.replace(OPENCODE_SESSION_REFRESH_ANY_REASON_PATTERN, ''); + const rawRemainder = removeOpenCodeSessionRefreshReasonRanges(markerText, reasonRanges); const remainder = rawRemainder.replace(/[().,;:\s-]+/g, ''); if (remainder.length === 0) { return true; @@ -204,6 +227,52 @@ function stripOpenCodeGenericApiErrorPrefix(message: string): string { return message.replace(/^opencode api error(?:[.:\s-]+|$)/i, ''); } +function findOpenCodeSessionRefreshReasonRanges(text: string): Array<[number, number]> { + const ranges: Array<[number, number]> = []; + for (const marker of OPENCODE_SESSION_REFRESH_REASON_MARKERS) { + const prefix = `${marker}:`; + let searchFrom = 0; + while (searchFrom < text.length) { + const markerStart = text.indexOf(prefix, searchFrom); + if (markerStart < 0) { + break; + } + const tokenStart = markerStart + prefix.length; + const tokenEnd = findOpenCodeSessionRefreshReasonTokenEnd(text, tokenStart); + if (tokenEnd !== null) { + ranges.push([markerStart, tokenEnd]); + } + searchFrom = Math.max(tokenStart + 1, tokenEnd ?? tokenStart); + } + } + return ranges.sort(([left], [right]) => left - right); +} + +function findOpenCodeSessionRefreshReasonTokenEnd(text: string, start: number): number | null { + let end = start; + while (end < text.length && OPENCODE_SESSION_REFRESH_REASON_CHARS.includes(text[end] ?? '')) { + end += 1; + } + + const token = text.slice(start, end); + const arrowIndex = token.indexOf('->'); + if (arrowIndex <= 0 || arrowIndex >= token.length - 2) { + return null; + } + return end; +} + +function removeOpenCodeSessionRefreshReasonRanges( + text: string, + ranges: ReadonlyArray<[number, number]> +): string { + let result = text; + for (const [start, end] of [...ranges].sort(([left], [right]) => right - left)) { + result = `${result.slice(0, start)}${result.slice(end)}`; + } + return result; +} + function isBenignOpenCodeSessionRefreshRemainder(rawRemainder: string): boolean { if (OPENCODE_SESSION_REFRESH_FAILURE_PATTERN.test(rawRemainder)) { return false; diff --git a/src/shared/utils/sentryConfig.ts b/src/shared/utils/sentryConfig.ts index 02b74ffa..20447267 100644 --- a/src/shared/utils/sentryConfig.ts +++ b/src/shared/utils/sentryConfig.ts @@ -27,17 +27,29 @@ export function isValidDsn(dsn: string | undefined): dsn is string { const REDACTED = '[redacted]'; const MAX_REDACTION_DEPTH = 8; -const SENSITIVE_KEY_PATTERN = - /(token|secret|authorization|cookie|email|account|clientid|project|repo|path|cwd|teamname|sessionid|taskid|username|user_name)/i; +const SENSITIVE_KEY_PARTS = [ + 'token', + 'secret', + 'authorization', + 'cookie', + 'email', + 'account', + 'clientid', + 'project', + 'repo', + 'path', + 'cwd', + 'teamname', + 'sessionid', + 'taskid', + 'username', + 'user_name', +]; -const SENSITIVE_STRING_PATTERNS: Array<[RegExp, string]> = [ +const SENSITIVE_STRING_PATTERNS: [RegExp, string][] = [ [/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, REDACTED], [/\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi, REDACTED], - [/\b(?:sk|pk|rk|ghp|gho|github_pat|xoxb|xoxp|ya29)[A-Za-z0-9_\-]{12,}\b/g, REDACTED], - [ - /\b[A-Z0-9_]*(?:API_KEY|AUTH_TOKEN|TOKEN|SECRET|PASSWORD|AUTHORIZATION)[A-Z0-9_]*\s*=\s*("[^"]*"|'[^']*'|\S+)/gi, - REDACTED, - ], + [/\b(?:sk|pk|rk|ghp|gho|github_pat|xoxb|xoxp|ya29)[A-Za-z0-9_-]{12,}\b/g, REDACTED], [/\/Users\/[^/\s"'`]+(?:\/[^\s"'`]+)*/g, '/Users/[redacted]/[redacted-path]'], [/\/home\/[^/\s"'`]+(?:\/[^\s"'`]+)*/g, '/home/[redacted]/[redacted-path]'], [/([A-Za-z]:\\Users\\)[^\\\s"'`]+(?:\\[^\\\s"'`]+)*/g, '$1[redacted]\\[redacted-path]'], @@ -82,10 +94,24 @@ export function filterSafeSentryIntegrations current.replace(pattern, replacement), value ); + return redactSentryEnvAssignments(redacted); +} + +function isSensitiveSentryKey(key: string): boolean { + const normalized = key.toLowerCase(); + return SENSITIVE_KEY_PARTS.some((part) => normalized.includes(part)); +} + +function redactSentryEnvAssignments(value: string): string { + return value.replace(/\b[A-Z0-9_]+\s*=\s*("[^"]*"|'[^']*'|\S+)/gi, (match) => { + const separatorIndex = match.indexOf('='); + const key = match.slice(0, separatorIndex).trim(); + return isSensitiveSentryKey(key) ? REDACTED : match; + }); } function redactSentryValue(value: unknown, depth: number, seen: WeakSet): unknown { @@ -109,7 +135,7 @@ function redactSentryValue(value: unknown, depth: number, seen: WeakSet) const redacted: Record = {}; for (const [key, entry] of Object.entries(value)) { - redacted[key] = SENSITIVE_KEY_PATTERN.test(key) + redacted[key] = isSensitiveSentryKey(key) ? REDACTED : redactSentryValue(entry, depth + 1, seen); }