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[]> {
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;

View file

@ -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;

View file

@ -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<TIntegration extends SentryIntegrat
}
function redactSentryString(value: string): string {
return SENSITIVE_STRING_PATTERNS.reduce(
const redacted = SENSITIVE_STRING_PATTERNS.reduce(
(current, [pattern, replacement]) => 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<object>): unknown {
@ -109,7 +135,7 @@ function redactSentryValue(value: unknown, depth: number, seen: WeakSet<object>)
const redacted: Record<string, unknown> = {};
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);
}