fix(lint): simplify release validation patterns

This commit is contained in:
777genius 2026-05-22 01:24:23 +03:00
parent ad984a859b
commit c26a1d727a
3 changed files with 117 additions and 23 deletions

View file

@ -1341,8 +1341,7 @@ export class AgentTeamsMcpHttpServer {
): Promise<number[]> { ): Promise<number[]> {
const ownedPids = [rootPid]; const ownedPids = [rootPid];
const visited = new Set(ownedPids); const visited = new Set(ownedPids);
for (let index = 0; index < ownedPids.length; index += 1) { for (const parentPid of ownedPids) {
const parentPid = ownedPids[index];
for (const row of rows) { for (const row of rows) {
if (row.ppid !== parentPid || visited.has(row.pid)) { if (row.ppid !== parentPid || visited.has(row.pid)) {
continue; continue;

View file

@ -75,15 +75,26 @@ const MAX_DIAGNOSTIC_ITEMS = 20;
const MAX_PERMISSION_REQUEST_IDS = 10; const MAX_PERMISSION_REQUEST_IDS = 10;
const SECRET_FLAG_PATTERN = const SECRET_FLAG_PATTERN =
/(--(?:api-key|token|password|secret|authorization|auth-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi; /(--(?:api-key|token|password|secret|authorization|auth-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi;
const SECRET_VALUE_PATTERN = const SECRET_VALUE_PATTERNS: [RegExp, string][] = [
/\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; [/\bsk-\S{12,}\b/gi, '[redacted]'],
const OPENCODE_SESSION_REFRESH_REASON_PATTERN = [/\b[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g, '[redacted]'],
/\b(?:resolved_behavior_changed|opencode_app_mcp_transport_changed):[-a-z0-9._~/=]+->[-a-z0-9._~/=]+/i; ];
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 = const OPENCODE_SESSION_REFRESH_FAILURE_PATTERN =
// eslint-disable-next-line sonarjs/regex-complexity -- Keyword taxonomy is kept literal to preserve diagnostic behavior. // 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; /(?:^|[_\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 = 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; /\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 { ): string | undefined {
const trimmed = value?.replace(/\s+/g, ' ').trim(); const trimmed = value?.replace(/\s+/g, ' ').trim();
if (!trimmed) return undefined; if (!trimmed) return undefined;
const redacted = trimmed const redacted = redactDiagnosticEnvAssignments(
.replace(SECRET_FLAG_PATTERN, '$1[redacted]') SECRET_VALUE_PATTERNS.reduce(
.replace(SECRET_VALUE_PATTERN, '[redacted]'); (current, [pattern, replacement]) => current.replace(pattern, replacement),
trimmed.replace(SECRET_FLAG_PATTERN, '$1[redacted]')
)
);
return redacted.length > maxLength return redacted.length > maxLength
? `${redacted.slice(0, Math.max(0, maxLength - 3))}...` ? `${redacted.slice(0, Math.max(0, maxLength - 3))}...`
: redacted; : 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 { function boundedNumber(value: number | undefined): number | undefined {
return typeof value === 'number' && Number.isFinite(value) && value > 0 return typeof value === 'number' && Number.isFinite(value) && value > 0
? Math.trunc(value) ? Math.trunc(value)
@ -181,14 +203,15 @@ function isRecoverableOpenCodeSessionRefreshText(value: string | undefined): boo
) { ) {
return true; return true;
} }
if (!OPENCODE_SESSION_REFRESH_REASON_PATTERN.test(refreshText)) { const reasonRanges = findOpenCodeSessionRefreshReasonRanges(refreshText);
if (reasonRanges.length === 0) {
return false; return false;
} }
const markerText = refreshText; const markerText = refreshText;
if (hasOpenCodeSessionRefreshFailureConflict(markerText)) { if (hasOpenCodeSessionRefreshFailureConflict(markerText)) {
return false; return false;
} }
const rawRemainder = markerText.replace(OPENCODE_SESSION_REFRESH_ANY_REASON_PATTERN, ''); const rawRemainder = removeOpenCodeSessionRefreshReasonRanges(markerText, reasonRanges);
const remainder = rawRemainder.replace(/[().,;:\s-]+/g, ''); const remainder = rawRemainder.replace(/[().,;:\s-]+/g, '');
if (remainder.length === 0) { if (remainder.length === 0) {
return true; return true;
@ -204,6 +227,52 @@ function stripOpenCodeGenericApiErrorPrefix(message: string): string {
return message.replace(/^opencode api error(?:[.:\s-]+|$)/i, ''); 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 { function isBenignOpenCodeSessionRefreshRemainder(rawRemainder: string): boolean {
if (OPENCODE_SESSION_REFRESH_FAILURE_PATTERN.test(rawRemainder)) { if (OPENCODE_SESSION_REFRESH_FAILURE_PATTERN.test(rawRemainder)) {
return false; return false;

View file

@ -27,17 +27,29 @@ export function isValidDsn(dsn: string | undefined): dsn is string {
const REDACTED = '[redacted]'; const REDACTED = '[redacted]';
const MAX_REDACTION_DEPTH = 8; const MAX_REDACTION_DEPTH = 8;
const SENSITIVE_KEY_PATTERN = const SENSITIVE_KEY_PARTS = [
/(token|secret|authorization|cookie|email|account|clientid|project|repo|path|cwd|teamname|sessionid|taskid|username|user_name)/i; '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[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[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(?: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,
],
[/\/Users\/[^/\s"'`]+(?:\/[^\s"'`]+)*/g, '/Users/[redacted]/[redacted-path]'], [/\/Users\/[^/\s"'`]+(?:\/[^\s"'`]+)*/g, '/Users/[redacted]/[redacted-path]'],
[/\/home\/[^/\s"'`]+(?:\/[^\s"'`]+)*/g, '/home/[redacted]/[redacted-path]'], [/\/home\/[^/\s"'`]+(?:\/[^\s"'`]+)*/g, '/home/[redacted]/[redacted-path]'],
[/([A-Za-z]:\\Users\\)[^\\\s"'`]+(?:\\[^\\\s"'`]+)*/g, '$1[redacted]\\[redacted-path]'], [/([A-Za-z]:\\Users\\)[^\\\s"'`]+(?:\\[^\\\s"'`]+)*/g, '$1[redacted]\\[redacted-path]'],
@ -82,10 +94,24 @@ export function filterSafeSentryIntegrations<TIntegration extends SentryIntegrat
} }
function redactSentryString(value: string): string { function redactSentryString(value: string): string {
return SENSITIVE_STRING_PATTERNS.reduce( const redacted = SENSITIVE_STRING_PATTERNS.reduce(
(current, [pattern, replacement]) => current.replace(pattern, replacement), (current, [pattern, replacement]) => current.replace(pattern, replacement),
value 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<object>): unknown { function redactSentryValue(value: unknown, depth: number, seen: WeakSet<object>): unknown {
@ -109,7 +135,7 @@ function redactSentryValue(value: unknown, depth: number, seen: WeakSet<object>)
const redacted: Record<string, unknown> = {}; const redacted: Record<string, unknown> = {};
for (const [key, entry] of Object.entries(value)) { for (const [key, entry] of Object.entries(value)) {
redacted[key] = SENSITIVE_KEY_PATTERN.test(key) redacted[key] = isSensitiveSentryKey(key)
? REDACTED ? REDACTED
: redactSentryValue(entry, depth + 1, seen); : redactSentryValue(entry, depth + 1, seen);
} }