Feature/#63 - 시크릿 자동 감지 UseCase (DVDomain) - #65
Conversation
- DetectionResult · ServiceCandidate · DetectionConfidence · SubDetection - DetectedMetadata enum + JWT/Database/PEMKey/Certificate/JSONCredential/envSet 서브 struct
- PrefixRule · RegexRule · PEMHeaderRule · DatabaseSchemeRule VO - BuiltInPrefixRules(AI 5개) · BuiltInPEMHeaders(8개) · BuiltInDatabaseSchemes(13개) · BuiltInRegexRules - 긴 prefix/헤더가 먼저 매칭되도록 all은 길이 내림차순으로 반환
- SensitiveString: withUnsafeAccess seam · constant-time eq · deinit best-effort zero-fill - InputNormalizer: trim + 64KB 상한 - SecretPatternRepository protocol + BundledSecretPatternRepository - SecretDetector · DetectorContext 프로토콜 (재귀 파이프라인 seam)
- DetectSecretUseCase protocol + DetectSecretUseCaseImpl (6-step fall-through 파이프라인) - 구현체: PrefixRegexDetector · PEMDetector · DatabaseURLDetector - 스텁: EnvSetDetector · JSONCredentialDetector · JWTDetector · Base64URL
- DetectSecretUseCaseImpl 골든 6 · PrefixRegexDetector 6 · PEMDetector 4 · DatabaseURLDetector 5 (총 21건) - DetectionFixture: 프리픽스만 진짜인 fake 토큰 · PEM 세 종류 · DB URL 세 종류 - 미구현 Detector 3개(EnvSet · JSONCredential · JWT)는 파일 스켈레톤만
- Base64URL.decode: `-`/`_` 치환 + padding 보정 후 표준 base64 디코드 - JWTDetector: eyJ prefix + 3-part 분해 → header/payload JSON → alg · iss · sub · exp metadata - alg=none · 서명 빈 3-part도 허용 (omittingEmptySubsequences: false) - DetectionFixture.jwt(header:payload:signature:) 테스트 헬퍼 추가
- 2줄 이상 KEY=VALUE(UPPER_SNAKE_CASE) 매칭 시 각 VALUE를 DetectorContext.detect 로 재귀 감지 - `#` 주석 · 빈 라인 · 잘못된 KEY 라인은 무시 - 큰따옴표 · 작은따옴표로 감싼 VALUE는 벗겨서 하위 detector에 전달 - envSet metadata에 감지된 KEY 배열 채움
- JSONSerialization으로 root dict 파싱 후 구조적 힌트로 종류 판별 - type=service_account: GCP · (본문 `firebase` 포함 시) Firebase (project_id/client_email 파싱) - installed/web.client_id: Google OAuth Client (redirect_uris 파싱) - aws_access_key_id: AWS Credentials (최상위 · 하위 dict 모두 스캔) - client_id + client_secret only: Generic OAuth (.medium)
- Prefix: ai(10) · payments(13) · devops(12) · cloud(3) · communication(9) · monitoring(2) · saas(10) · oauth(1) = 60개 - Regex: Twilio · Mailgun · Mailchimp · Telegram · Discord · Sentry DSN = 6개 - Airtable(`pat`)는 `.` context 요구로 HubSpot(`pat-na1-`/`pat-eu1-`)과 구분 - 카테고리별 sanity + AKIA minLength + pat 충돌 방지 + Twilio/Sentry regex 테스트 추가
- URL 파싱 실패 시 Azure Storage · Service Bus · SQL 커넥션 문자열 fallback 매칭 - Azure는 candidate만 부여 (호스트/포트를 URL 파서로 뽑을 수 없어 metadata 미채움) - Azure 3종 테스트 추가
- JWT 파싱 · Anthropic · GitHub PAT · Stripe Live · Slack · AWS · Twilio · GCP Service Account - Google OAuth Client · .env 재귀 · Azure Storage · Neon 호스트 · OpenSSH ed25519 - DVDomain 전체 131건 그린 (기존 111 + 20)
- InputNormalizer: 64KB 자르기가 UTF-8 multi-byte 문자를 Latin-1로 오변환하던 버그 수정 (문자 단위 누적) - Base64URL: base64URL alphabet 밖 문자(`+`·`/`·`=`)를 조기 거절 - BuiltInPrefixRules: EAA(Meta) minLength 50, Airtable pat minLength 20으로 오탐 완화 - JSONCredentialDetector: Firebase 판별을 project_id·client_email로 국한 (64KB raw lowercase 회피) - PrefixRegexDetector: regex를 init에서 pre-compile해 매 호출 재컴파일 제거 - 테스트 +12건: UTF-8 boundary · Twitter/Meta/Airtable minLength · Discord embedded substring · .env value=a=b=c · JWT array payload · Base64URL alphabet
Walkthrough시크릿 탐지 모델과 보호된 입력 래퍼를 추가했다. 규칙 저장소를 통해 prefix, regex, PEM, 데이터베이스 규칙을 공급한다. 환경 변수, JSON credential, JWT, PEM, 데이터베이스 URL을 탐지하고 결과를 통합한다. Changes시크릿 감지 기능
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant DetectSecretUseCaseImpl
participant EnvSetDetector
participant PrefixRegexDetector
participant DetectionResult
Caller->>DetectSecretUseCaseImpl: execute(value: SensitiveString)
DetectSecretUseCaseImpl->>EnvSetDetector: detect(value, context)
EnvSetDetector->>DetectSecretUseCaseImpl: recursively detect each environment value
DetectSecretUseCaseImpl->>PrefixRegexDetector: detect(value, context)
PrefixRegexDetector->>DetectionResult: return candidates and metadata
DetectSecretUseCaseImpl->>Caller: return first non-nil result
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (4)
Projects/DVDomain/Tests/Core/Detection/DetectSecretUseCaseImplTests.swift (1)
21-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win두 번째 후보 검증을 추가하세요.
테스트 이름은 "Stability + OpenAI 후보 둘 다"를 명시합니다. 하지만 assertion은
candidates.count와 index 0만 확인합니다. index 1의service가 실제로"OpenAI"인지 검증하지 않습니다. 두 번째 candidate에 대한 assertion을 추가하면 테스트 이름과 실제 검증 범위가 일치합니다.✅ 제안하는 추가 assertion
`#expect`(result.candidates.count == 2) `#expect`(result.candidates[0].service == "Stability AI") `#expect`(result.candidates[0].confidence == .high) + `#expect`(result.candidates[1].service == "OpenAI")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVDomain/Tests/Core/Detection/DetectSecretUseCaseImplTests.swift` around lines 21 - 28, Update the stabilityContext test to assert that candidates[1].service equals "OpenAI", while preserving the existing count, first-candidate service, and confidence assertions.Projects/DVDomain/Tests/Core/Support/DetectionFixture.swift (1)
47-56: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
try!대신 안전한 처리로 교체하세요.52-53행에서
try!를 사용합니다.JSONSerialization.data가 실패하면 테스트 전체가 크래시로 종료됩니다. 리터럴 dictionary라 실패 가능성은 낮지만, 강제 연산은 피해야 합니다.try?로 옵셔널을 받고guard let으로 실패 시Issue.record를 남기는 방식으로 교체하세요.As per path instructions, "강제 언래핑(
!) 사용 시 반드시 지적하고guard let/if let/??대안을 제시하세요."🛡️ 제안하는 수정
static func jwt( header: [String: Any] = ["alg": "HS256", "typ": "JWT"], payload: [String: Any], signature: String = "sig" ) -> String { - let hd = try! JSONSerialization.data(withJSONObject: header, options: [.sortedKeys]) - let pd = try! JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]) + guard let hd = try? JSONSerialization.data(withJSONObject: header, options: [.sortedKeys]), + let pd = try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]) + else { + fatalError("DetectionFixture.jwt: header/payload를 직렬화할 수 없습니다.") + } return "\(base64URLEncode(hd)).\(base64URLEncode(pd)).\(signature)" }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVDomain/Tests/Core/Support/DetectionFixture.swift` around lines 47 - 56, Replace the force-try calls in the jwt helper with optional serialization and guard let checks for both header and payload data; on failure, record the issue with Issue.record and return an appropriate fallback so the test does not crash.Source: Path instructions
Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift (1)
6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
prefixRules를private으로 좁혀야 합니다.
prefixRules는detect(_:context:)내부에서만 쓰이는데 접근 제어자가 없어internal로 노출됩니다. 같은 타입의compiledRegex는 이미private입니다. 일관성과 캡슐화를 위해prefixRules도private으로 좁히세요.- let prefixRules: [PrefixRule] + private let prefixRules: [PrefixRule]As per path instructions, "접근 제어가 가능한 가장 엄격한 수준인지 확인하세요."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift` at line 6, PrefixRegexDetector의 prefixRules 프로퍼티를 private으로 변경해 타입 내부에서만 접근 가능하도록 하세요. detect(_:context:)의 사용 방식과 같은 타입의 compiledRegex 접근 수준은 유지하세요.Source: Path instructions
Projects/DVDomain/Sources/Detection/Security/SensitiveString.swift (1)
44-63: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
SensitiveBox의 접근 수준을fileprivate로 좁히세요.
bytes,init(bytes:),constantTimeEqual이 명시적 접근 제어 없이 기본값internal로 선언되어 있습니다. 이 멤버들은 같은 파일의SensitiveString에서만 사용됩니다.SensitiveBox는 주석에도 "외부 노출 금지"라고 명시되어 있으므로, 클래스와 멤버 모두fileprivate로 좁혀 모듈 전체에서 접근 가능한 범위를 줄이세요.As per path instructions, "접근 제어가 가능한 가장 엄격한 수준인지 확인하세요. (private > fileprivate > internal)".
🔒️ 접근 제어 축소 제안
-final class SensitiveBox: `@unchecked` Sendable { - var bytes: [UInt8] - - init(bytes: [UInt8]) { self.bytes = bytes } +fileprivate final class SensitiveBox: `@unchecked` Sendable { + fileprivate var bytes: [UInt8] + + fileprivate init(bytes: [UInt8]) { self.bytes = bytes } deinit { // ... } - static func constantTimeEqual(_ a: [UInt8], _ b: [UInt8]) -> Bool { + fileprivate static func constantTimeEqual(_ a: [UInt8], _ b: [UInt8]) -> Bool { guard a.count == b.count else { return false } var diff: UInt8 = 0 for i in 0..<a.count { diff |= a[i] ^ b[i] } return diff == 0 } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Projects/DVDomain/Sources/Detection/Security/SensitiveString.swift` around lines 44 - 63, Restrict SensitiveBox and its members bytes, init(bytes:), and constantTimeEqual to fileprivate, since they are only used by SensitiveString in the same file. Preserve their existing behavior while ensuring none remain internal.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Projects/DVDomain/Sources/Detection/Detector/DatabaseURLDetector.swift`:
- Around line 19-30: Update detectAsAzureConnectionString so the Azure SQL
branch requires an Azure-specific host marker such as .database.windows.net in
addition to Server= and Database=. Preserve the existing Azure Storage and Azure
Service Bus checks, and return nil when the generic SQL markers lack the Azure
host marker.
In `@Projects/DVDomain/Sources/Detection/Detector/DetectorContext.swift`:
- Around line 9-11: DetectorContext.detect(_:)를 통해 재진입하는 감지 파이프라인에 재귀 깊이 상태와 상한을
전달하도록 수정하고, 중첩된 KEY=VALUE 처리로 상한을 초과하면 추가 감지를 즉시 중단하세요. EnvSetDetector의
context.detect(...) 호출과 execute(value:) 진입 경로 모두에서 기존 감지 동작은 제한 이내에 유지하세요.
In `@Projects/DVDomain/Sources/Detection/Detector/EnvSetDetector.swift`:
- Around line 50-59: Remove the forced unwrapping of value.first and value.last
in stripQuotes by binding both properties with guard let and returning value
when either is unavailable. Preserve the existing quote-pair detection and
stripping behavior.
In `@Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift`:
- Around line 41-50: Update the guard condition in the compiledRegex loop to
unwrap the result of regex.wholeMatch(in:) without the redundant match != nil
check, since the unwrapped match is only used for presence validation and not
referenced afterward.
In `@Projects/DVDomain/Sources/Detection/Model/DetectionConfidence.swift`:
- Around line 10-13: Update DetectionConfidence’s static < operator to replace
the rank dictionary and forced unwraps with switch-based comparison that
explicitly handles every confidence case. Preserve the ordering low < medium <
high, and let exhaustive switching require updates when new cases are added.
In `@Projects/DVDomain/Sources/Detection/Rule/BuiltInDatabaseSchemes.swift`:
- Line 16: Update the amqps entry in BuiltInDatabaseSchemes to use defaultPort
5671 instead of 5672, and ensure DatabaseURLDetector’s default-port handling
includes amqps so URLs without an explicit port resolve to 5671.
In `@Projects/DVDomain/Sources/Detection/Rule/BuiltInPrefixRules.swift`:
- Around line 73-74: Remove the Mailgun `key-` entry from the built-in prefix
rules so `PrefixRegexDetector.detect` cannot short-circuit the matching
`key-[a-f0-9]{32}` rule in `BuiltInRegexRules.swift`; retain the regex-only
detection with `.high` confidence.
In `@Projects/DVDomain/Sources/Detection/Security/SensitiveString.swift`:
- Around line 31-33: Update redactedPrefix so secrets whose length is less than
or equal to n are not returned in full; apply a masking-only representation for
that case, while preserving the existing prefixed redaction format for longer
secrets.
In `@Projects/DVDomain/Sources/UseCase/Impl/Secret/DetectSecretUseCaseImpl.swift`:
- Around line 27-46: DetectSecretUseCaseImpl의 execute(value:)와
DetectorContext.detect(_:)에 실행 depth를 추가해 재귀적인 EnvSetDetector 호출을 제한하세요. 최상위
execute는 초기 depth로 시작하고, detect(_:)는 현재 depth를 계승한 뒤 한 단계 증가시켜 파이프라인을 실행하도록 하세요.
설정된 최대 depth에 도달하면 추가 detector 실행을 중단하고 .none을 반환하며, 서로 다른 execute 호출 간 depth가
누적되지 않도록 보장하세요.
---
Nitpick comments:
In `@Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift`:
- Line 6: PrefixRegexDetector의 prefixRules 프로퍼티를 private으로 변경해 타입 내부에서만 접근 가능하도록
하세요. detect(_:context:)의 사용 방식과 같은 타입의 compiledRegex 접근 수준은 유지하세요.
In `@Projects/DVDomain/Sources/Detection/Security/SensitiveString.swift`:
- Around line 44-63: Restrict SensitiveBox and its members bytes, init(bytes:),
and constantTimeEqual to fileprivate, since they are only used by
SensitiveString in the same file. Preserve their existing behavior while
ensuring none remain internal.
In `@Projects/DVDomain/Tests/Core/Detection/DetectSecretUseCaseImplTests.swift`:
- Around line 21-28: Update the stabilityContext test to assert that
candidates[1].service equals "OpenAI", while preserving the existing count,
first-candidate service, and confidence assertions.
In `@Projects/DVDomain/Tests/Core/Support/DetectionFixture.swift`:
- Around line 47-56: Replace the force-try calls in the jwt helper with optional
serialization and guard let checks for both header and payload data; on failure,
record the issue with Issue.record and return an appropriate fallback so the
test does not crash.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 66353ada-ffca-46f7-8f70-faab96f54164
📒 Files selected for processing (37)
Projects/DVDomain/Sources/Detection/Detector/DatabaseURLDetector.swiftProjects/DVDomain/Sources/Detection/Detector/DetectorContext.swiftProjects/DVDomain/Sources/Detection/Detector/EnvSetDetector.swiftProjects/DVDomain/Sources/Detection/Detector/JSONCredentialDetector.swiftProjects/DVDomain/Sources/Detection/Detector/JWTDetector.swiftProjects/DVDomain/Sources/Detection/Detector/PEMDetector.swiftProjects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swiftProjects/DVDomain/Sources/Detection/Detector/SecretDetector.swiftProjects/DVDomain/Sources/Detection/Model/DetectedMetadata.swiftProjects/DVDomain/Sources/Detection/Model/DetectionConfidence.swiftProjects/DVDomain/Sources/Detection/Model/DetectionResult.swiftProjects/DVDomain/Sources/Detection/Model/ServiceCandidate.swiftProjects/DVDomain/Sources/Detection/Model/SubDetection.swiftProjects/DVDomain/Sources/Detection/Rule/BuiltInDatabaseSchemes.swiftProjects/DVDomain/Sources/Detection/Rule/BuiltInPEMHeaders.swiftProjects/DVDomain/Sources/Detection/Rule/BuiltInPrefixRules.swiftProjects/DVDomain/Sources/Detection/Rule/BuiltInRegexRules.swiftProjects/DVDomain/Sources/Detection/Rule/BundledSecretPatternRepository.swiftProjects/DVDomain/Sources/Detection/Rule/DatabaseSchemeRule.swiftProjects/DVDomain/Sources/Detection/Rule/PEMHeaderRule.swiftProjects/DVDomain/Sources/Detection/Rule/PrefixRule.swiftProjects/DVDomain/Sources/Detection/Rule/RegexRule.swiftProjects/DVDomain/Sources/Detection/Security/SensitiveString.swiftProjects/DVDomain/Sources/Detection/Support/Base64URL.swiftProjects/DVDomain/Sources/Detection/Support/InputNormalizer.swiftProjects/DVDomain/Sources/Repository/Interface/SecretPatternRepository.swiftProjects/DVDomain/Sources/UseCase/Impl/Secret/DetectSecretUseCaseImpl.swiftProjects/DVDomain/Sources/UseCase/Interface/Secret/DetectSecretUseCase.swiftProjects/DVDomain/Tests/Core/Detection/DatabaseURLDetectorTests.swiftProjects/DVDomain/Tests/Core/Detection/DetectSecretUseCaseImplTests.swiftProjects/DVDomain/Tests/Core/Detection/EnvSetDetectorTests.swiftProjects/DVDomain/Tests/Core/Detection/InputNormalizerTests.swiftProjects/DVDomain/Tests/Core/Detection/JSONCredentialDetectorTests.swiftProjects/DVDomain/Tests/Core/Detection/JWTDetectorTests.swiftProjects/DVDomain/Tests/Core/Detection/PEMDetectorTests.swiftProjects/DVDomain/Tests/Core/Detection/PrefixRegexDetectorTests.swiftProjects/DVDomain/Tests/Core/Support/DetectionFixture.swift
| private func detectAsAzureConnectionString(_ raw: String) -> DetectionResult? { | ||
| if raw.contains("DefaultEndpointsProtocol=") && raw.contains("AccountName=") { | ||
| return azureResult(service: "Azure Storage", label: "Azure Storage Connection String") | ||
| } | ||
| if raw.contains("Endpoint=sb://") { | ||
| return azureResult(service: "Azure Service Bus", label: "Azure Service Bus Connection String") | ||
| } | ||
| if raw.contains("Server=") && raw.contains("Database=") { | ||
| return azureResult(service: "Azure SQL", label: "Azure SQL Connection String") | ||
| } | ||
| return nil | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Azure SQL 판별 조건이 너무 넓어 오탐 위험이 있습니다.
"Server="와 "Database="만으로 Azure SQL을 판별하면, 온프레미스 SQL Server나 다른 ADO.NET 커넥션 문자열도 Azure SQL로 잘못 분류될 수 있습니다. 다른 두 조건(DefaultEndpointsProtocol=, Endpoint=sb://)은 Azure 고유 마커라 안전하지만, 이 조건만 예외입니다. PR 목표가 서비스 자동 채움이므로, 잘못된 서비스가 채워지면 사용자 경험에 직접 영향을 줍니다. .database.windows.net 같은 Azure 전용 호스트 마커를 추가 조건으로 요구하세요.
🎯 Azure 전용 마커 추가로 오탐 축소
- if raw.contains("Server=") && raw.contains("Database=") {
+ if raw.contains("Server=") && raw.contains("Database=")
+ && raw.lowercased().contains("database.windows.net") {
return azureResult(service: "Azure SQL", label: "Azure SQL Connection String")
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private func detectAsAzureConnectionString(_ raw: String) -> DetectionResult? { | |
| if raw.contains("DefaultEndpointsProtocol=") && raw.contains("AccountName=") { | |
| return azureResult(service: "Azure Storage", label: "Azure Storage Connection String") | |
| } | |
| if raw.contains("Endpoint=sb://") { | |
| return azureResult(service: "Azure Service Bus", label: "Azure Service Bus Connection String") | |
| } | |
| if raw.contains("Server=") && raw.contains("Database=") { | |
| return azureResult(service: "Azure SQL", label: "Azure SQL Connection String") | |
| } | |
| return nil | |
| } | |
| private func detectAsAzureConnectionString(_ raw: String) -> DetectionResult? { | |
| if raw.contains("DefaultEndpointsProtocol=") && raw.contains("AccountName=") { | |
| return azureResult(service: "Azure Storage", label: "Azure Storage Connection String") | |
| } | |
| if raw.contains("Endpoint=sb://") { | |
| return azureResult(service: "Azure Service Bus", label: "Azure Service Bus Connection String") | |
| } | |
| if raw.contains("Server=") && raw.contains("Database=") | |
| && raw.lowercased().contains("database.windows.net") { | |
| return azureResult(service: "Azure SQL", label: "Azure SQL Connection String") | |
| } | |
| return nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Detector/DatabaseURLDetector.swift`
around lines 19 - 30, Update detectAsAzureConnectionString so the Azure SQL
branch requires an Azure-specific host marker such as .database.windows.net in
addition to Server= and Database=. Preserve the existing Azure Storage and Azure
Service Bus checks, and return nil when the generic SQL markers lack the Azure
host marker.
| protocol DetectorContext: Sendable { | ||
| func detect(_ value: SensitiveString) -> DetectionResult | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd DetectSecretUseCaseImpl.swift EnvSetDetector.swift
rg -n -B2 -A20 'func detect\(' Projects/DVDomain/Sources/UseCase/Impl/Secret/DetectSecretUseCaseImpl.swift
rg -n -B2 -A30 'struct EnvSetDetector' Projects/DVDomain/Sources/Detection/Detector/EnvSetDetector.swiftRepository: DevaultProject/Devault-macOS
Length of output: 2085
🏁 Script executed:
#!/bin/bash
set -e
sed -n '1,120p' Projects/DVDomain/Sources/UseCase/Impl/Secret/DetectSecretUseCaseImpl.swift
printf '\n--- SecretDetector definitions/usages ---\n'
rg -n 'protocol SecretDetector|struct EnvSetDetector|func detect\(_ value: SensitiveString, context: DetectorContext\)|class|struct.*SecretDetector|DetectSecretUseCaseImpl' Projects/DVDomain/SourcesRepository: DevaultProject/Devault-macOS
Length of output: 4330
detect(_:) 재귀 깊이 제한을 추가하세요.
EnvSetDetector가 context.detect(...)로 VALUE를 다시 파이프라인에 넘기고, DetectorContext.detect(_:)가 execute(value:)를 그대로 호출합니다. 이 사이클이 반복되면 중첩된 KEY=VALUE 페이로드에서 재귀 깊이가 제한 없이 늘어납니다. DetectorContext.detect(_:) 또는 파이프라인 진입점에 깊이 제한을 두고 그 값을 전달한 뒤, 상한을 초과하면 재귀 감지를 중단하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Detector/DetectorContext.swift` around
lines 9 - 11, DetectorContext.detect(_:)를 통해 재진입하는 감지 파이프라인에 재귀 깊이 상태와 상한을 전달하도록
수정하고, 중첩된 KEY=VALUE 처리로 상한을 초과하면 추가 감지를 즉시 중단하세요. EnvSetDetector의
context.detect(...) 호출과 execute(value:) 진입 경로 모두에서 기존 감지 동작은 제한 이내에 유지하세요.
| /// 앞뒤 짝이 맞는 `"` 또는 `'`만 벗겨낸다. 내부에 escape된 따옴표(`\"`)는 별도 처리 없이 그대로 남긴다. | ||
| private func stripQuotes(_ value: String) -> String { | ||
| guard value.count >= 2 else { return value } | ||
| let first = value.first! | ||
| let last = value.last! | ||
| if (first == "\"" && last == "\"") || (first == "'" && last == "'") { | ||
| return String(value.dropFirst().dropLast()) | ||
| } | ||
| return value | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
강제 언래핑 제거 필요.
value.first!, value.last!는 직전의 value.count >= 2 검사로 현재는 안전합니다. 하지만 이 함수를 이후 수정할 때 앞의 길이 검사가 바뀌면 크래시로 이어질 위험이 있습니다. guard let 패턴으로 교체해 이 위험을 원천 차단하세요.
경로 지침에 따라 강제 언래핑(!) 사용 시 guard let / if let / ?? 대안을 제시해야 합니다: "강제 언래핑(!) 사용 시 반드시 지적하고 guard let / if let / ?? 대안을 제시하세요."
🛡️ 제안하는 수정
private func stripQuotes(_ value: String) -> String {
guard value.count >= 2 else { return value }
- let first = value.first!
- let last = value.last!
+ guard let first = value.first, let last = value.last else { return value }
if (first == "\"" && last == "\"") || (first == "'" && last == "'") {
return String(value.dropFirst().dropLast())
}
return value
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// 앞뒤 짝이 맞는 `"` 또는 `'`만 벗겨낸다. 내부에 escape된 따옴표(`\"`)는 별도 처리 없이 그대로 남긴다. | |
| private func stripQuotes(_ value: String) -> String { | |
| guard value.count >= 2 else { return value } | |
| let first = value.first! | |
| let last = value.last! | |
| if (first == "\"" && last == "\"") || (first == "'" && last == "'") { | |
| return String(value.dropFirst().dropLast()) | |
| } | |
| return value | |
| } | |
| /// 앞뒤 짝이 맞는 `"` 또는 `'`만 벗겨낸다. 내부에 escape된 따옴표(`\"`)는 별도 처리 없이 그대로 남긴다. | |
| private func stripQuotes(_ value: String) -> String { | |
| guard value.count >= 2 else { return value } | |
| guard let first = value.first, let last = value.last else { return value } | |
| if (first == "\"" && last == "\"") || (first == "'" && last == "'") { | |
| return String(value.dropFirst().dropLast()) | |
| } | |
| return value | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Detector/EnvSetDetector.swift` around
lines 50 - 59, Remove the forced unwrapping of value.first and value.last in
stripQuotes by binding both properties with guard let and returning value when
either is unavailable. Preserve the existing quote-pair detection and stripping
behavior.
Source: Path instructions
| for (rule, regex) in compiledRegex { | ||
| guard let match = try? regex.wholeMatch(in: raw), match != nil else { continue } | ||
| return DetectionResult(candidates: [ | ||
| ServiceCandidate( | ||
| service: rule.service, | ||
| displayLabel: rule.displayLabel, | ||
| confidence: rule.confidence | ||
| ) | ||
| ]) | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file =="
git ls-files | rg 'Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift|swift' | head -50 || true
echo "== file excerpt =="
if [ -f Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift ]; then
nl -ba Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift | sed -n '1,120p'
fi
echo "== Swift availability =="
if command -v swift >/dev/null 2>&1; then
swift --version
tmpdir="$(mktemp -d)"
cat > "$tmpdir/Probe.swift" <<'SWIFT'
public struct DetectionResult: Equatable {
public var candidates: [String]
}
`@available`(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
`@available`(macCatalyst 15.0, *)
public func probe<Regex: RegexComponent>(target: String, regex: Regex) -> DetectionResult {
do {
for (rule, r) in [(0, regex)] {
guard let match = try? r.wholeMatch(in: target), match != nil else { continue }
return DetectionResult(candidates: ["ok"])
}
} catch {
}
return DetectionResult(candidates: [])
}
`@available`(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
`@available`(macCatalyst 15.0, *)
let result = probe(target: "abc", regex: #"abc"#)
print(result)
SWIFT
swiftc "$tmpdir/Probe.swift" -o "$tmpdir/Probe" 2>&1 || true
else
echo "swift not available"
fi
echo "== Search related patterns =="
rg -n "guard let match = try? .*wholeMatch|wholeMatch\\(in:|Match\\?|Match! nil" .Repository: DevaultProject/Devault-macOS
Length of output: 3440
🌐 Web query:
Swift 5.7 SE-0230 throw/throws? optional return flatten try?
💡 Result:
In Swift, the behavior requested in SE-0230 was implemented in Swift 5.0 [1][2]. This change ensures that the try? operator does not produce redundant, nested optionals when applied to an expression that already returns an optional value [1][3]. Before this change, applying try? to an expression that returned an optional (e.g., T?) would result in a double-optional (e.g., T??), which was often considered counter-intuitive and required manual flattening [4][5]. Since Swift 5.0, the try? operator exhibits the following behavior: - If the sub-expression returns a non-optional value, it is wrapped in an Optional (e.g., T becomes T?) [1][3]. - If the sub-expression already returns an Optional, no additional level of optionality is added; the result remains at a single level of optionality (e.g., T? remains T?) [1][3][2]. This aligns try? with other Swift features like optional chaining (?.) and conditional type casting (as?), which similarly collapse optional types rather than nesting them [2][6]. There is no automatic flattening of multiple existing levels of optionality (e.g., T?? would not become T? solely via try?); the operator simply ensures it does not introduce a new level of nesting when the underlying expression is already optional [7].
Citations:
- 1: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0230-flatten-optional-try.md
- 2: https://www.hackingwithswift.com/swift/5.0/flattening-optionals
- 3: https://github.com/apple/swift-evolution/blob/main/proposals/0230-flatten-optional-try.md
- 4: https://forums.swift.org/t/accepted-se-230-flatten-nested-optionals-resulting-from-try/17376
- 5: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20180108/042799.html
- 6: https://www.hackingwithswift.com/articles/147/swift-5-is-changing-optional-try
- 7: https://forums.swift.org/t/se-0230-flatten-nested-optionals-resulting-from-try/16570
중복된 nil 체크를 제거하세요.
Swift의 try? regex.wholeMatch(in: raw)는 이미 Match?를 반환하므로 guard let match = ... 뒤에서 match는 optional이 아닙니다. 이 뒤의 match != nil은 항상 참이라 제거하고, 존재 여부만 검사하세요. match는 이후 값으로도 사용하지 않습니다.
🧹 제안 수정
for (rule, regex) in compiledRegex {
- guard let match = try? regex.wholeMatch(in: raw), match != nil else { continue }
+ guard (try? regex.wholeMatch(in: raw)) != nil else { continue }
return DetectionResult(candidates: [📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (rule, regex) in compiledRegex { | |
| guard let match = try? regex.wholeMatch(in: raw), match != nil else { continue } | |
| return DetectionResult(candidates: [ | |
| ServiceCandidate( | |
| service: rule.service, | |
| displayLabel: rule.displayLabel, | |
| confidence: rule.confidence | |
| ) | |
| ]) | |
| } | |
| for (rule, regex) in compiledRegex { | |
| guard (try? regex.wholeMatch(in: raw)) != nil else { continue } | |
| return DetectionResult(candidates: [ | |
| ServiceCandidate( | |
| service: rule.service, | |
| displayLabel: rule.displayLabel, | |
| confidence: rule.confidence | |
| ) | |
| ]) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift`
around lines 41 - 50, Update the guard condition in the compiledRegex loop to
unwrap the result of regex.wholeMatch(in:) without the redundant match != nil
check, since the unwrapped match is only used for presence validation and not
referenced afterward.
| public static func < (lhs: Self, rhs: Self) -> Bool { | ||
| let rank: [Self: Int] = [.low: 0, .medium: 1, .high: 2] | ||
| return rank[lhs]! < rank[rhs]! | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
강제 언래핑 제거 필요.
rank[lhs]!와 rank[rhs]!는 강제 언래핑입니다. 지금은 rank 딕셔너리가 3개 case를 모두 포함해 안전하지만, 나중에 case를 추가하고 rank를 갱신하지 않으면 런타임 크래시가 발생합니다. switch 기반 비교로 바꾸면 컴파일러가 모든 case를 강제해 이 위험을 없앱니다.
🛡️ 제안 수정
public static func < (lhs: Self, rhs: Self) -> Bool {
- let rank: [Self: Int] = [.low: 0, .medium: 1, .high: 2]
- return rank[lhs]! < rank[rhs]!
+ func rank(_ value: Self) -> Int {
+ switch value {
+ case .low: return 0
+ case .medium: return 1
+ case .high: return 2
+ }
+ }
+ return rank(lhs) < rank(rhs)
}As per path instructions, "강제 언래핑(!) 사용 시 반드시 지적하고 guard let / if let / ?? 대안을 제시하세요."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public static func < (lhs: Self, rhs: Self) -> Bool { | |
| let rank: [Self: Int] = [.low: 0, .medium: 1, .high: 2] | |
| return rank[lhs]! < rank[rhs]! | |
| } | |
| public static func < (lhs: Self, rhs: Self) -> Bool { | |
| func rank(_ value: Self) -> Int { | |
| switch value { | |
| case .low: return 0 | |
| case .medium: return 1 | |
| case .high: return 2 | |
| } | |
| } | |
| return rank(lhs) < rank(rhs) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Model/DetectionConfidence.swift` around
lines 10 - 13, Update DetectionConfidence’s static < operator to replace the
rank dictionary and forced unwraps with switch-based comparison that explicitly
handles every confidence case. Preserve the ordering low < medium < high, and
let exhaustive switching require updates when new cases are added.
Source: Path instructions
| .init(scheme: "redis", defaultPort: 6379), | ||
| .init(scheme: "rediss", defaultPort: 6379), | ||
| .init(scheme: "amqp", defaultPort: 5672), | ||
| .init(scheme: "amqps", defaultPort: 5672), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate target files =="
fd -a 'BuiltInDatabaseSchemes\.swift|DatabaseURLDetector.*Tests\.swift|DatabaseURLDetector' . || true
echo
echo "== Target file excerpt =="
file="Projects/DVDomain/Sources/Detection/Rule/BuiltInDatabaseSchemes.swift"
if [ -f "$file" ]; then
nl -ba "$file" | sed -n '1,80p'
fi
echo
echo "== Search URL detector references =="
rg -n "BuiltInDatabaseSchemes|DatabaseURLDetector|defaultPort|amqps|amqp" Projects/DVDomain -S || true
echo
echo "== Tests around port default behavior =="
file2="$(fd 'DatabaseURLDetector.*Tests\.swift' Projects/DVDomain/Tests | head -n1 || true)"
if [ -n "${file2:-}" ]; then
nl -ba "$file2" | sed -n '1,240p'
fi
echo
echo "== Behavioral probe from source constants and detector implementation =="
python3 - <<'PY'
from pathlib import Path
import re
for p in Path('Projects/DVDomain').rglob('*.swift'):
s=p.read_text(errors='ignore')
if 'amqps' in s or 'defaultPort' in s:
print(f'--- {p} ---')
for i,line in enumerate(s.splitlines(),1):
if 'amqps' in line or 'defaultPort' in line or 'DatabaseURLDetector' in line or 'fillsDefaultPort' in line:
print(f'{i}: {line}')
PYRepository: DevaultProject/Devault-macOS
Length of output: 528
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Target file excerpt =="
cat -n Projects/DVDomain/Sources/Detection/Rule/BuiltInDatabaseSchemes.swift | sed -n '1,80p'
echo
echo "== Database URL detector excerpts =="
cat -n Projects/DVDomain/Sources/Detection/Detector/DatabaseURLDetector.swift | sed -n '1,220p'
echo
echo "== Tests excerpt =="
cat -n Projects/DVDomain/Tests/Core/Detection/DatabaseURLDetectorTests.swift | sed -n '1,260p'
echo
echo "== Relevant occurrences =="
grep -RInE 'amqps|amqp|defaultPort|fillsDefaultPort|default port|port absent|port number' Projects/DVDomain/Tests Projects/DVDomain/Sources/Detection 2>/dev/null || true
echo
echo "== Source-derived mapping =="
python3 - <<'PY'
from pathlib import Path
import re
p = Path('Projects/DVDomain/Sources/Detection/Rule/BuiltInDatabaseSchemes.swift')
src = p.read_text(errors='ignore')
for m in re.finditer(r'\.init\s*\(\s*scheme\s*:\s*"([^"]+)"\s*,\s*defaultPort\s*:\s*([0-9]+)\s*\)', src):
print(f'{m.group(1)}: {m.group(2)}')
PYRepository: DevaultProject/Devault-macOS
Length of output: 11196
🌐 Web query:
RFC 1982 AMQPS default port 5671 IANA assigned
💡 Result:
The Internet Assigned Numbers Authority (IANA) has officially assigned port 5671 for "amqps," which is defined as the AMQP protocol over TLS/SSL [1][2]. This registration for both TCP and UDP was completed on March 26, 2008 [1][3]. There is no connection between this assignment and RFC 1982. RFC 1982, titled "Serial Number Arithmetic," is a technical document related to the Domain Name System (DNS) and serial number management, and it does not define or pertain to AMQP or its associated network ports [4].
Citations:
- 1: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=5671
- 2: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=AMQP
- 3: https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?age=1&page=95
- 4: https://datatracker.ietf.org/doc/html/rfc1982
AMQPS 기본 포트를 5671로 교정하세요.
amqps의 defaultPort가 현재 5672로 같습니다. 5672는 평문 AMQP 포트라서 URL 포트가 없는 amqps 문자열을 감지하면 DatabaseURLDetector가 잘못된 기본 포트로 채웁니다. AMQPS에는 5671을 사용하세요.
🐛 수정안
.init(scheme: "amqp", defaultPort: 5672),
- .init(scheme: "amqps", defaultPort: 5672),
+ .init(scheme: "amqps", defaultPort: 5671),기본 포트 반환 케이스를 amqps에도 포함해 regress를 막는 것이 좋습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Rule/BuiltInDatabaseSchemes.swift` at
line 16, Update the amqps entry in BuiltInDatabaseSchemes to use defaultPort
5671 instead of 5672, and ensure DatabaseURLDetector’s default-port handling
includes amqps so URLs without an explicit port resolve to 5671.
| .init(prefix: "key-", minLength: 36, | ||
| service: "Mailgun", displayLabel: "Mailgun API Key", confidence: .medium), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Mailgun 정규식 규칙이 이 prefix 규칙에 가려져 도달하지 못합니다.
key- prefix 규칙(minLength 36, confidence .medium)은 BuiltInRegexRules.swift의 key-[a-f0-9]{32} 정규식(confidence .high)과 같은 서비스(Mailgun)를 대상으로 합니다. 정확한 Mailgun 키 형식(총 36자)은 이 prefix 조건(hasPrefix("key-") && count >= 36)을 항상 만족합니다.
PrefixRegexDetector.detect는 prefixMatches가 있으면 정규식 검사 전에 즉시 반환합니다. 결과적으로 정확한 형식을 검증하는 고신뢰도(.high) 정규식 규칙은 절대 실행되지 않고, 실제 Mailgun 키는 항상 .medium confidence로만 분류됩니다. PrefixRegexDetectorTests.swift의 mailgunRegex 테스트(161-165행)도 이 때문에 실제로는 regex 경로가 아니라 prefix 경로를 테스트하고 있습니다.
이 prefix 규칙을 제거하고 정규식 규칙만 유지하거나, prefix minLength를 37 이상으로 올려 정규식 규칙과 겹치지 않게 하세요.
♻️ 수정안 예시 (prefix 규칙 제거)
.init(prefix: "oauth:", service: "Twitch", displayLabel: "Twitch IRC OAuth Token", confidence: .high),
.init(prefix: "AAAA", minLength: 80,
service: "Twitter", displayLabel: "Twitter Bearer Token", confidence: .medium),
.init(prefix: "EAA", minLength: 50,
service: "Meta", displayLabel: "Meta Access Token", confidence: .medium),
](key- prefix 항목 삭제 — BuiltInRegexRules.swift의 정확한 형식 검증 규칙만 유지)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Rule/BuiltInPrefixRules.swift` around
lines 73 - 74, Remove the Mailgun `key-` entry from the built-in prefix rules so
`PrefixRegexDetector.detect` cannot short-circuit the matching
`key-[a-f0-9]{32}` rule in `BuiltInRegexRules.swift`; retain the regex-only
detection with `.high` confidence.
| public func redactedPrefix(_ n: Int = 4) -> String { | ||
| withUnsafeAccess { "\($0.prefix(n))*** (len=\($0.count))" } | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
redactedPrefix가 짧은 시크릿을 마스킹 없이 그대로 노출합니다.
원본 길이가 n(기본 4) 이하이면 $0.prefix(n)이 문자열 전체를 반환합니다. 이 경우 마스킹 표시(***) 뒤에 원본 전체가 이미 노출된 상태입니다. 짧은 시크릿일수록 로그 유출 위험이 더 커지므로, 길이 조건을 추가해 전체 노출을 막으세요.
🛡️ 짧은 문자열 전체 노출 방지
public func redactedPrefix(_ n: Int = 4) -> String {
- withUnsafeAccess { "\($0.prefix(n))*** (len=\($0.count))" }
+ withUnsafeAccess {
+ guard $0.count > n else { return "*** (len=\($0.count))" }
+ return "\($0.prefix(n))*** (len=\($0.count))"
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public func redactedPrefix(_ n: Int = 4) -> String { | |
| withUnsafeAccess { "\($0.prefix(n))*** (len=\($0.count))" } | |
| } | |
| public func redactedPrefix(_ n: Int = 4) -> String { | |
| withUnsafeAccess { | |
| guard $0.count > n else { return "*** (len=\($0.count))" } | |
| return "\($0.prefix(n))*** (len=\($0.count))" | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/Detection/Security/SensitiveString.swift` around
lines 31 - 33, Update redactedPrefix so secrets whose length is less than or
equal to n are not returned in full; apply a masking-only representation for
that case, while preserving the existing prefixed redaction format for longer
secrets.
| public func execute(value: SensitiveString) -> DetectionResult { | ||
| value.withUnsafeAccess { raw in | ||
| let normalized = InputNormalizer.normalize(raw) | ||
| guard !normalized.isEmpty else { return .none } | ||
|
|
||
| let wrapped = SensitiveString(normalized) | ||
| for detector in detectors { | ||
| if let result = detector.detect(wrapped, context: self) { | ||
| return result | ||
| } | ||
| } | ||
| return .none | ||
| } | ||
| } | ||
|
|
||
| /// `DetectorContext` 채택. `EnvSetDetector`가 각 KEY의 VALUE에 대해 재귀적으로 파이프라인을 호출할 수 있게 한다. | ||
| public func detect(_ value: SensitiveString) -> DetectionResult { | ||
| execute(value: value) | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: EnvSetDetector와 DetectorContext에서 재귀 깊이 제한 로직이 있는지 확인
fd -t f 'EnvSetDetector.swift|DetectorContext.swift' Projects/DVDomain/Sources | xargs -I{} sh -c 'echo "--- {} ---"; cat -n {}'Repository: DevaultProject/Devault-macOS
Length of output: 3610
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "--- Candidate files containing DetectSecretUseCaseImpl, DetectorContext, SecretDetector, ExecutionContext references ---"
rg -n "DetectSecretUseCaseImpl|protocol DetectorContext|struct .*Detector|detect\\(_ value: SensitiveString|execute\\(value:|context: DetectorContext" Projects/DVDomain/Sources || true
echo
echo "--- Use Case file context ---"
file=$(fd -t f 'DetectSecretUseCaseImpl.swift' Projects/DVDomain/Sources | head -n1 || true)
if [ -n "${file:-}" ]; then
wc -l "$file"
cat -n "$file"
fiRepository: DevaultProject/Devault-macOS
Length of output: 5140
재귀 호출 깊이를 제한하도록 DetectorContext에 depth 상태를 추가하세요.
EnvSetDetector는 각 VALUE에서 context.detect(SensitiveString(entry.value))을 호출해 전체 detector 파이프라인을 다시 실행합니다. 현재 DetectorContext에는 depth가 없으므로 중첩된 env-set 입력에서 계속 6단계 감지가 반복됩니다. execute(value:) 진입점과 DetectorContext.detect(_:)를 함께 제어할 수 있도록 실행 depth를 계승/제한하는 방식으로 안정성을 맞춰 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Projects/DVDomain/Sources/UseCase/Impl/Secret/DetectSecretUseCaseImpl.swift`
around lines 27 - 46, DetectSecretUseCaseImpl의 execute(value:)와
DetectorContext.detect(_:)에 실행 depth를 추가해 재귀적인 EnvSetDetector 호출을 제한하세요. 최상위
execute는 초기 depth로 시작하고, detect(_:)는 현재 depth를 계승한 뒤 한 단계 증가시켜 파이프라인을 실행하도록 하세요.
설정된 최대 depth에 도달하면 추가 detector 실행을 중단하고 .none을 반환하며, 서로 다른 execute 호출 간 depth가
누적되지 않도록 보장하세요.
✨ What's this PR?
📌 관련 이슈 (Related Issue)
🧶 주요 변경 내용 (Summary)
Domain 모델 · 규칙 카탈로그
DetectionResult·ServiceCandidate·DetectionConfidence·SubDetection·DetectedMetadata(JWT · Database · PEMKey · Certificate · JSONCredential · envSet)PrefixRule·RegexRule·PEMHeaderRule·DatabaseSchemeRule) +BundledSecretPatternRepositoryBuiltInPrefixRules(60 rule · AI/Payments/DevOps/Cloud/Communication/Monitoring/SaaS/OAuth 서브네임스페이스) ·BuiltInRegexRules(6 rule · Twilio · Mailgun · Mailchimp · Telegram · Discord · Sentry) ·BuiltInPEMHeaders(8) ·BuiltInDatabaseSchemes(13)UseCase 파이프라인
DetectSecretUseCaseprotocol +DetectSecretUseCaseImpl—EnvSet → JSONCredential → PEM → DatabaseURL → PrefixRegex → JWT6-step fall-throughSecretDetector·DetectorContextprotocol (env-set 재귀를 위한 seam)PrefixRegexDetector·PEMDetector·DatabaseURLDetector(Azure key=value fallback 포함) ·JWTDetector·EnvSetDetector(재귀) ·JSONCredentialDetector(GCP · Firebase · Google OAuth · AWS · Generic)보안 · 유틸
SensitiveString+SensitiveBox— 클로저 스코프 raw 접근 · constant-time eq · deinit best-effort zero-fill · description은 length만 노출InputNormalizer— trim + 64KB 문자 경계 안전 자르기 (UTF-8 safe)Base64URL.decode— alphabet 검증 + padding 보정보안 정책
os_log/Logger/printimport 금지 (grep 검증됨)DetectionResult가 raw 시크릿 값을 절대 담지 않도록 타입으로 보장🧪 테스트 / 검증 내역
DetectSecretUseCaseImpl골든 §1~6 커버리지 18건SensitiveString/InputNormalizer서포트 ·DetectionFixture💬 기타 공유 사항
didPasteValue(SensitiveString)Action · chip UI · 폼 auto-fill)은 후속 이슈로 이월 (Feature: Secret Creation View 개발 #41 F1)SECRET_DETECTION_PATTERNS.md§297)의 우선순위를 그대로 따름 — 컨테이너 포맷(env · JSON)이 개별 값 매칭보다 앞SecretPatternRepository를 다른 구현체로 스왑하는 후속 이슈SensitiveString타입 방어 + 로깅 사고 차단 + best-effort wipe로 실질 방어선 구성🙇🏻♀️ 리뷰 가이드
DetectSecretUseCaseImpl.execute(value:)— 파이프라인 순서 · fall-through 로직 · env-set 재귀SensitiveString.swift— 로깅 사고 차단 방어 계층 (description 마스킹 · constant-time eq · deinit wipe · DEBUG-only.testing()factory)PrefixRegexDetector—requiresContextletter-only 최적화 (Airtable.심볼 컨텍스트에서 raw 전체 lowercasing 회피)BuiltInPrefixRules.all— 프리컴퓨트 + prefix 길이 내림차순 정렬 (짧은 prefix가 긴 prefix보다 먼저 매칭돼 오탐하는 것 방지)JSONCredentialDetector.detectServiceAccount— Firebase 판별을project_id/client_email로 국한Summary by CodeRabbit
새 기능
테스트