fix(lint): simplify release validation patterns
This commit is contained in:
parent
ad984a859b
commit
c26a1d727a
3 changed files with 117 additions and 23 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue