Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@ and `froggy-sre`, run `make ecosystem-smoke`.
if the screen hasn't changed.
- **Secret redaction** — `Redactor` strips AWS keys, GitHub PATs,
Anthropic / OpenAI / Slack tokens, JWTs, bearer headers,
`password=`/`api_key=`/... values, and Luhn-validated credit cards
**before** anything is written to disk.
`password=`/`api_key=`/... values (quoted values and a label with its
value on the next OCR line included), multi-line PEM blocks, and
Luhn-validated credit cards **before** anything is written to disk.
OCR lines are redacted as one block, not line by line. E-mail addresses
and IBANs are **not** redacted — see `SECURITY.md`.
- **Sliding context window** — the last 30 redacted snapshots, returned
on demand as a single text block.
- **MLX inference in a child process** — `FroggyMLXWorker` runs in its
Expand Down
5 changes: 4 additions & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ Claude Code ←— stdio / MCP (JSON-RPC) —→ froggy-sre ←— socket (pr
OCR не запускается.
- **Secret redaction** — `Redactor` режет AWS-ключи, GitHub PAT, Anthropic /
OpenAI / Slack-токены, JWT, bearer-заголовки, `password=`/`api_key=`/...
и валидированные по Luhn кредитки **до** записи на диск.
(включая значения в кавычках и значение на следующей OCR-строке),
многострочные PEM-блоки и валидированные по Luhn кредитки **до** записи
на диск. OCR-строки редактируются одним блоком, а не построчно. E-mail
и IBAN **не** редактируются — см. `SECURITY.md`.
- **Sliding context window** — последние 30 redacted-снапшотов, по запросу
отдаются как текстовый блок.
- **MLX-инференс в child process** — `FroggyMLXWorker` живёт в отдельном
Expand Down
2 changes: 2 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Include: macOS version, Froggy build, reproduction steps, and what you expected
- Context-dependent secrets (e.g. a UUID that happens to be a session token) are not detected
- Partial redaction (e.g. first 4 chars leaked) is possible if a pattern has an off-by-one error

Redactor limits (built-in rules, 2026-09): OCR lines are joined and redacted as one block, so multi-line PEM blocks and a `password:` label with its value on the next line are caught, and quoted values are taken whole. E-mail addresses, IBANs, phone numbers and passwords without a recognisable label are **not** redacted. Meeting transcripts written by `SessionStore` do not pass through `Redactor` at all. Corporate patterns can be added via `~/Library/Application Support/Froggy/redaction-rules.json`.

If you find a pattern that reliably bypasses Redactor for a real-world secret format, please report it — that is a meaningful security issue.

### `VortexCore/IPC.swift` — inter-process socket
Expand Down
8 changes: 8 additions & 0 deletions SECURITY.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ Froggy разработан исходя из предположения, что
контекстно-специфические секреты (внутренние URL, имена контактов,
медицинские данные, внутренние кодовые названия проектов). Считайте
редакцию защитой по принципу best-effort defence-in-depth, а не гарантией.
- Границы встроенных правил `Redactor` (2026-09): OCR-строки склеиваются и
редактируются одним блоком, поэтому многострочные PEM-блоки и метка
`password:` со значением на следующей строке перехватываются, значения
в кавычках берутся целиком. E-mail, IBAN, номера телефонов и пароли без
распознаваемой метки **не** редактируются. Транскрипты созвонов
(`SessionStore`) через `Redactor` не проходят вовсе. Корпоративные
паттерны добавляются через
`~/Library/Application Support/Froggy/redaction-rules.json`.
- Entitlement `task_for_pid-allow` требуется для работы стратегии pageout
`machVM` на сторонних процессах. Apple выдаёт его редко; стратегия `jetsam`
по умолчанию работает без него. Смотрите `packaging/README.md` и ADR 0007.
2 changes: 2 additions & 0 deletions Sources/FroggyDaemon/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ struct FroggyDaemon {
audioOnDeviceRecognition: config.audioOnDeviceRecognition,
echoSuppressionEnabled: config.echoSuppressionEnabled,
echoSuppressionTailMs: config.echoSuppressionTailMs,
vadEnabled: config.vadEnabled,
vadRmsThreshold: config.vadRmsThreshold,
freezingEnabled: config.freezingEnabled,
auditLog: auditLog,
// Issue #59: при pressure level change coordinator дёргает
Expand Down
32 changes: 27 additions & 5 deletions Sources/LushaBridge/Redactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,17 @@ public struct Redactor: Sendable {
return Self.redactCreditCards(in: s)
}

/// OCR отдаёт экран построчно, а секреты живут поперёк строк: PEM-блок
/// — это три и более строк, `password:` часто стоит НАД значением.
/// Построчный `map` не матчил ни одно из них. Поэтому строки склеиваются
/// через `\n`, правила применяются к целому блоку (`\s` в
/// NSRegularExpression матчит и перевод строки — label-правила ловят
/// значение на следующей строке сами), результат режется обратно.
/// Число строк может уменьшиться: многострочный матч схлопывается
/// в один маркер. Вызывающие принимают `[String]` любой длины.
public func redact(_ lines: [String]) -> [String] {
lines.map(redact)
guard !lines.isEmpty else { return [] }
return redact(lines.joined(separator: "\n")).components(separatedBy: "\n")
}

// MARK: - Built-in rules
Expand Down Expand Up @@ -76,15 +85,19 @@ public struct Redactor: Sendable {
replacement: "[REDACTED-BEARER]",
caseInsensitive: true
),
// Значение в кавычках берём целиком (`password="correct horse
// battery staple"` — иначе `\S+` оставлял хвост после первого
// пробела). Внутри кавычек перевод строки не допускаем, чтобы
// незакрытая кавычка не съела следующую OCR-строку.
.init(
name: "password-label",
pattern: "(password|passwd|pwd)\\s*[:=]\\s*\\S+",
pattern: "(password|passwd|pwd)\\s*[:=]\\s*(?:\"[^\"\\n]*\"|'[^'\\n]*'|\\S+)",
replacement: "$1=[REDACTED]",
caseInsensitive: true
),
.init(
name: "secret-label",
pattern: "(api[_-]?key|secret|token)\\s*[:=]\\s*[\"']?[A-Za-z0-9_\\-\\.]{8,}[\"']?",
pattern: "(api[_-]?key|secret|token)\\s*[:=]\\s*(?:\"[^\"\\n]*\"|'[^'\\n]*'|[\"']?[A-Za-z0-9_\\-\\.]{8,}[\"']?)",
replacement: "$1=[REDACTED]",
caseInsensitive: true
),
Expand Down Expand Up @@ -113,8 +126,12 @@ public struct Redactor: Sendable {

// MARK: - Credit cards (Luhn-validated, отдельно от regex-rules)

/// Разделители групп: пробел, дефис и «типографские» пробелы, которые
/// Vision OCR ставит вместо обычного (NBSP U+00A0, narrow NBSP U+202F,
/// thin space U+2009). Перевод строки не входит — номер на двух строках
/// не склеиваем.
private static let cardCandidatePattern: NSRegularExpression? = {
try? NSRegularExpression(pattern: "\\b\\d[\\d \\-]{11,21}\\d\\b")
try? NSRegularExpression(pattern: "\\b\\d[\\d \\u00A0\\u202F\\u2009\\-]{11,21}\\d\\b")
}()

private static func redactCreditCards(in text: String) -> String {
Expand Down Expand Up @@ -165,7 +182,12 @@ private struct CompiledRule: Sendable {
let replacement: String

init?(_ rule: RedactionRule) {
var options: NSRegularExpression.Options = []
// OCR-строки редактируются одним склеенным блоком (см. `redact(_ lines:)`),
// поэтому `^`/`$` должны матчить границы строк, а не всего блока —
// иначе пользовательские правила вида `^ACME-\d{6}$` из
// redaction-rules.json перестали бы ловить отдельную строку.
// Встроенные правила якорей не содержат, для них это no-op.
var options: NSRegularExpression.Options = [.anchorsMatchLines]
if rule.caseInsensitive { options.insert(.caseInsensitive) }
guard let regex = try? NSRegularExpression(pattern: rule.pattern, options: options) else {
return nil
Expand Down
Loading
Loading