/
Alex_Ural
/
opencode
Обзор
Документация
Войти
/
Alex_Ural
/
opencode
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
packages/http-recorder/src/redaction.ts
117 строк
4 KB
Kit Langton
feat(http-recorder): prepare public beta release (#31018)
06 июн 2026, 06:01
Не верифицирован
06 июн 2026, 06:01
54f4974
Код
Авторство
О чём код?
import { Schema } from "effect" export const REDACTED = "[REDACTED]" const DEFAULT_REDACT_HEADERS = [ "authorization", "cookie", "proxy-authorization", "set-cookie", "x-api-key", "x-amz-security-token", "x-goog-api-key", ] const DEFAULT_REDACT_QUERY = [ "access_token", "api-key", "api_key", "apikey", "code", "key", "signature", "sig", "token", "x-amz-credential", "x-amz-security-token", "x-amz-signature", ] const SECRET_PATTERNS: ReadonlyArray<{ readonly label: string; readonly pattern: RegExp }> = [ { label: "bearer token", pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}\b/i }, { label: "API key", pattern: /\bsk-[A-Za-z0-9][A-Za-z0-9_-]{20,}\b/ }, { label: "Anthropic API key", pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/ }, { label: "Google API key", pattern: /\bAIza[0-9A-Za-z_-]{20,}\b/ }, { label: "AWS access key", pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/ }, { label: "GitHub token", pattern: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/ }, { label: "private key", pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ }, ] const ENV_SECRET_NAMES = /(?:API|AUTH|BEARER|CREDENTIAL|KEY|PASSWORD|SECRET|TOKEN)/i const SAFE_ENV_VALUES = new Set(["fixture", "test", "test-key"]) const envSecrets = () => Object.entries(process.env).flatMap(([name, value]) => { if (!value) return [] if (!ENV_SECRET_NAMES.test(name)) return [] if (value.length < 12) return [] if (SAFE_ENV_VALUES.has(value.toLowerCase())) return [] return [{ name, value }] }) const pathFor = (base: string, key: string) => (base ? `${base}.${key}` : key) const stringEntries = (value: unknown, base = ""): ReadonlyArray<{ readonly path: string; readonly value: string }> => { if (typeof value === "string") return [{ path: base, value }] if (Array.isArray(value)) return value.flatMap((item, index) => stringEntries(item, `${base}[${index}]`)) if (value && typeof value === "object") { return Object.entries(value).flatMap(([key, child]) => stringEntries(child, pathFor(base, key))) } return [] } const redactionSet = (values: ReadonlyArray<string> | undefined, defaults: ReadonlyArray<string>) => new Set([...defaults, ...(values ?? [])].map((value) => value.toLowerCase())) export type UrlRedactor = (url: string) => string export const redactUrl = ( raw: string, query: ReadonlyArray<string> = DEFAULT_REDACT_QUERY, urlRedactor?: UrlRedactor, ) => { if (!URL.canParse(raw)) return urlRedactor?.(raw) ?? raw const url = new URL(raw) if (url.username) url.username = REDACTED if (url.password) url.password = REDACTED const redacted = redactionSet(query, DEFAULT_REDACT_QUERY) for (const key of url.searchParams.keys()) { if (redacted.has(key.toLowerCase())) url.searchParams.set(key, REDACTED) } return urlRedactor?.(url.toString()) ?? url.toString() } export const redactHeaders = ( headers: Record<string, string>, allow: ReadonlyArray<string>, redact: ReadonlyArray<string> = DEFAULT_REDACT_HEADERS, ) => { const allowed = new Set(allow.map((name) => name.toLowerCase())) const redacted = redactionSet(redact, DEFAULT_REDACT_HEADERS) return Object.fromEntries( Object.entries(headers) .map(([name, value]) => [name.toLowerCase(), value] as const) .filter(([name]) => allowed.has(name)) .map(([name, value]) => [name, redacted.has(name) ? REDACTED : value] as const) .toSorted(([a], [b]) => a.localeCompare(b)), ) } export const SecretFindingSchema = Schema.Struct({ path: Schema.String, reason: Schema.String, }) export type SecretFinding = Schema.Schema.Type<typeof SecretFindingSchema> export const secretFindings = (value: unknown): ReadonlyArray<SecretFinding> => { const environment = envSecrets() return stringEntries(value).flatMap((entry) => [ ...SECRET_PATTERNS.filter((item) => item.pattern.test(entry.value)).map((item) => ({ path: entry.path, reason: item.label, })), ...environment .filter((item) => entry.value.includes(item.value)) .map((item) => ({ path: entry.path, reason: `environment secret ${item.name}` })), ]) }