Skip to content

Feature/#63 - 시크릿 자동 감지 UseCase (DVDomain) - #65

Merged
doyeonk429 merged 12 commits into
developfrom
feature/#63
Aug 5, 2026
Merged

Feature/#63 - 시크릿 자동 감지 UseCase (DVDomain)#65
doyeonk429 merged 12 commits into
developfrom
feature/#63

Conversation

@doyeonk429

@doyeonk429 doyeonk429 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✨ What's this PR?

📌 관련 이슈 (Related Issue)


🧶 주요 변경 내용 (Summary)

Domain 모델 · 규칙 카탈로그

  • DetectionResult · ServiceCandidate · DetectionConfidence · SubDetection · DetectedMetadata(JWT · Database · PEMKey · Certificate · JSONCredential · envSet)
  • Rule struct 4개(PrefixRule · RegexRule · PEMHeaderRule · DatabaseSchemeRule) + BundledSecretPatternRepository
  • BuiltInPrefixRules(60 rule · AI/Payments/DevOps/Cloud/Communication/Monitoring/SaaS/OAuth 서브네임스페이스) · BuiltInRegexRules(6 rule · Twilio · Mailgun · Mailchimp · Telegram · Discord · Sentry) · BuiltInPEMHeaders(8) · BuiltInDatabaseSchemes(13)

UseCase 파이프라인

  • DetectSecretUseCase protocol + DetectSecretUseCaseImplEnvSet → JSONCredential → PEM → DatabaseURL → PrefixRegex → JWT 6-step fall-through
  • SecretDetector · DetectorContext protocol (env-set 재귀를 위한 seam)
  • 각 detector 개별 구현: 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 보정

보안 정책

  • Detector 파일에서 os_log/Logger/print import 금지 (grep 검증됨)
  • DetectionResult가 raw 시크릿 값을 절대 담지 않도록 타입으로 보장

🧪 테스트 / 검증 내역

  • DVDomain 전체 149건 그린 (Detection 84 신규 · 기존 65)
    • DetectSecretUseCaseImpl 골든 §1~6 커버리지 18건
    • Detector 유닛 · SensitiveString/InputNormalizer 서포트 · DetectionFixture
  • 서브에이전트 코드 리뷰 P1 8건 + P2 5건 + nit 3건 전량 반영
    • UTF-8 안전 truncation · Base64URL alphabet 검증 · EAA/Airtable minLength 오탐 완화 · Firebase 판별 field-level · regex pre-compile 등
  • macOS build 그린

💬 기타 공유 사항

  • UI wiring 스코프 밖: DVPresentation 붙이는 작업(didPasteValue(SensitiveString) Action · chip UI · 폼 auto-fill)은 후속 이슈로 이월 (Feature: Secret Creation View 개발 #41 F1)
  • 감지 순서: 스펙(SECRET_DETECTION_PATTERNS.md §297)의 우선순위를 그대로 따름 — 컨테이너 포맷(env · JSON)이 개별 값 매칭보다 앞
  • 의도적 미도입: License-Key 포맷 · 고엔트로피 fallback은 서비스명을 만들지 못해 chip UI 부재 → 제외
  • Bundled vs Remote: 현재 룰은 앱 번들 정적 임베드. 원격 룰 로딩은 SecretPatternRepository를 다른 구현체로 스왑하는 후속 이슈
  • In-memory 암호화 미도입: Detector가 매칭하려면 어차피 평문 필요 — key를 옆에 들고 있는 셈이라 무의미. 대신 SensitiveString 타입 방어 + 로깅 사고 차단 + best-effort wipe로 실질 방어선 구성

🙇🏻‍♀️ 리뷰 가이드

  • DetectSecretUseCaseImpl.execute(value:) — 파이프라인 순서 · fall-through 로직 · env-set 재귀
  • SensitiveString.swift — 로깅 사고 차단 방어 계층 (description 마스킹 · constant-time eq · deinit wipe · DEBUG-only .testing() factory)
  • PrefixRegexDetectorrequiresContext letter-only 최적화 (Airtable . 심볼 컨텍스트에서 raw 전체 lowercasing 회피)
  • BuiltInPrefixRules.all — 프리컴퓨트 + prefix 길이 내림차순 정렬 (짧은 prefix가 긴 prefix보다 먼저 매칭돼 오탐하는 것 방지)
  • JSONCredentialDetector.detectServiceAccount — Firebase 판별을 project_id/client_email로 국한

Summary by CodeRabbit

  • 새 기능

    • API 키, JWT, PEM 키·인증서, 데이터베이스 연결 정보, JSON 자격 증명, 환경 변수 세트를 자동 탐지합니다.
    • 탐지 결과에 서비스 후보, 신뢰도, 구조화된 메타데이터를 제공합니다.
    • 주요 AI, 클라우드, 결제, SaaS 및 커뮤니케이션 서비스 패턴을 지원합니다.
    • 민감한 입력을 마스킹하고 안전하게 처리하며, 중첩된 환경 변수 값도 재귀적으로 분석합니다.
    • 입력 정규화와 다양한 연결 문자열 형식을 지원합니다.
  • 테스트

    • 주요 탐지 유형과 유효하지 않은 입력, 경계 조건에 대한 검증을 추가했습니다.

- 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
@doyeonk429 doyeonk429 linked an issue Aug 3, 2026 that may be closed by this pull request
27 tasks
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

시크릿 탐지 모델과 보호된 입력 래퍼를 추가했다. 규칙 저장소를 통해 prefix, regex, PEM, 데이터베이스 규칙을 공급한다. 환경 변수, JSON credential, JWT, PEM, 데이터베이스 URL을 탐지하고 결과를 통합한다.

Changes

시크릿 감지 기능

Layer / File(s) Summary
탐지 계약과 보안 입력 기반
Projects/DVDomain/Sources/Detection/Model/*, Projects/DVDomain/Sources/Detection/Security/SensitiveString.swift, Projects/DVDomain/Sources/Detection/Support/*, Projects/DVDomain/Sources/Detection/Detector/*, Projects/DVDomain/Sources/Detection/Rule/{PrefixRule,RegexRule,PEMHeaderRule,DatabaseSchemeRule}.swift, Projects/DVDomain/Sources/Repository/Interface/SecretPatternRepository.swift, Projects/DVDomain/Sources/UseCase/Interface/Secret/DetectSecretUseCase.swift, Projects/DVDomain/Tests/Core/Detection/InputNormalizerTests.swift
탐지 결과, 후보, 메타데이터, 신뢰도, 하위 탐지 모델을 추가했다. SensitiveString은 원문 접근 범위, 마스킹, constant-time 비교, zero-fill 저장소를 제공한다. 입력 정규화와 Base64URL 디코딩 계약을 추가했다.
규칙 카탈로그와 기본 detector
Projects/DVDomain/Sources/Detection/Rule/BuiltIn*.swift, Projects/DVDomain/Sources/Detection/Rule/BundledSecretPatternRepository.swift, Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift, Projects/DVDomain/Sources/Detection/Detector/PEMDetector.swift, Projects/DVDomain/Sources/Detection/Detector/DatabaseURLDetector.swift, Projects/DVDomain/Tests/Core/Detection/{PrefixRegexDetectorTests,PEMDetectorTests,DatabaseURLDetectorTests}.swift
번들 prefix, regex, PEM, 데이터베이스 규칙을 추가했다. prefix와 regex 매칭, PEM 메타데이터, 데이터베이스 URL 및 Azure 연결 문자열 탐지를 구현했다.
구조화된 credential과 재귀 detector
Projects/DVDomain/Sources/Detection/Detector/{EnvSetDetector,JSONCredentialDetector,JWTDetector}.swift, Projects/DVDomain/Tests/Core/Detection/{EnvSetDetectorTests,JSONCredentialDetectorTests,JWTDetectorTests}.swift
.env 항목의 재귀 감지를 추가했다. GCP, Firebase, Google OAuth, AWS, 일반 OAuth JSON credential을 분류한다. JWT header와 payload의 선택적 claim을 파싱한다.
탐지 파이프라인 통합
Projects/DVDomain/Sources/UseCase/Impl/Secret/DetectSecretUseCaseImpl.swift, Projects/DVDomain/Tests/Core/Detection/DetectSecretUseCaseImplTests.swift, Projects/DVDomain/Tests/Core/Support/DetectionFixture.swift
입력을 정규화한 뒤 EnvSet, JSONCredential, PEM, DatabaseURL, PrefixRegex, JWT 순서로 탐지한다. 첫 번째 결과를 반환하고, 모든 detector가 실패하면 .none을 반환한다. 통합 golden 테스트와 fixture를 추가했다.

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
Loading

Suggested labels: ✅ Test

Suggested reviewers: yeseonglee

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 DVDomain의 시크릿 자동 감지 UseCase 추가라는 주요 변경을 명확하게 설명합니다.
Linked Issues check ✅ Passed #63의 모델, 규칙 저장소, 보안 처리, detector, 파이프라인, 유틸리티 및 테스트 요구사항을 구현했습니다.
Out of Scope Changes check ✅ Passed 변경 사항은 #63의 시크릿 감지 도메인 범위와 관련된 구현 및 테스트로 구성되어 있습니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#63

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@doyeonk429 doyeonk429 self-assigned this Aug 3, 2026
@doyeonk429 doyeonk429 added the ✨ Feature 새로운 기능 개발 label Aug 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

prefixRulesprivate으로 좁혀야 합니다.

prefixRulesdetect(_:context:) 내부에서만 쓰이는데 접근 제어자가 없어 internal로 노출됩니다. 같은 타입의 compiledRegex는 이미 private입니다. 일관성과 캡슐화를 위해 prefixRulesprivate으로 좁히세요.

-    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

📥 Commits

Reviewing files that changed from the base of the PR and between ee3e731 and 57ea0ac.

📒 Files selected for processing (37)
  • Projects/DVDomain/Sources/Detection/Detector/DatabaseURLDetector.swift
  • Projects/DVDomain/Sources/Detection/Detector/DetectorContext.swift
  • Projects/DVDomain/Sources/Detection/Detector/EnvSetDetector.swift
  • Projects/DVDomain/Sources/Detection/Detector/JSONCredentialDetector.swift
  • Projects/DVDomain/Sources/Detection/Detector/JWTDetector.swift
  • Projects/DVDomain/Sources/Detection/Detector/PEMDetector.swift
  • Projects/DVDomain/Sources/Detection/Detector/PrefixRegexDetector.swift
  • Projects/DVDomain/Sources/Detection/Detector/SecretDetector.swift
  • Projects/DVDomain/Sources/Detection/Model/DetectedMetadata.swift
  • Projects/DVDomain/Sources/Detection/Model/DetectionConfidence.swift
  • Projects/DVDomain/Sources/Detection/Model/DetectionResult.swift
  • Projects/DVDomain/Sources/Detection/Model/ServiceCandidate.swift
  • Projects/DVDomain/Sources/Detection/Model/SubDetection.swift
  • Projects/DVDomain/Sources/Detection/Rule/BuiltInDatabaseSchemes.swift
  • Projects/DVDomain/Sources/Detection/Rule/BuiltInPEMHeaders.swift
  • Projects/DVDomain/Sources/Detection/Rule/BuiltInPrefixRules.swift
  • Projects/DVDomain/Sources/Detection/Rule/BuiltInRegexRules.swift
  • Projects/DVDomain/Sources/Detection/Rule/BundledSecretPatternRepository.swift
  • Projects/DVDomain/Sources/Detection/Rule/DatabaseSchemeRule.swift
  • Projects/DVDomain/Sources/Detection/Rule/PEMHeaderRule.swift
  • Projects/DVDomain/Sources/Detection/Rule/PrefixRule.swift
  • Projects/DVDomain/Sources/Detection/Rule/RegexRule.swift
  • Projects/DVDomain/Sources/Detection/Security/SensitiveString.swift
  • Projects/DVDomain/Sources/Detection/Support/Base64URL.swift
  • Projects/DVDomain/Sources/Detection/Support/InputNormalizer.swift
  • Projects/DVDomain/Sources/Repository/Interface/SecretPatternRepository.swift
  • Projects/DVDomain/Sources/UseCase/Impl/Secret/DetectSecretUseCaseImpl.swift
  • Projects/DVDomain/Sources/UseCase/Interface/Secret/DetectSecretUseCase.swift
  • Projects/DVDomain/Tests/Core/Detection/DatabaseURLDetectorTests.swift
  • Projects/DVDomain/Tests/Core/Detection/DetectSecretUseCaseImplTests.swift
  • Projects/DVDomain/Tests/Core/Detection/EnvSetDetectorTests.swift
  • Projects/DVDomain/Tests/Core/Detection/InputNormalizerTests.swift
  • Projects/DVDomain/Tests/Core/Detection/JSONCredentialDetectorTests.swift
  • Projects/DVDomain/Tests/Core/Detection/JWTDetectorTests.swift
  • Projects/DVDomain/Tests/Core/Detection/PEMDetectorTests.swift
  • Projects/DVDomain/Tests/Core/Detection/PrefixRegexDetectorTests.swift
  • Projects/DVDomain/Tests/Core/Support/DetectionFixture.swift

Comment on lines +19 to +30
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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +9 to +11
protocol DetectorContext: Sendable {
func detect(_ value: SensitiveString) -> DetectionResult
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.swift

Repository: 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/Sources

Repository: DevaultProject/Devault-macOS

Length of output: 4330


detect(_:) 재귀 깊이 제한을 추가하세요.

EnvSetDetectorcontext.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:) 진입 경로 모두에서 기존 감지 동작은 제한 이내에 유지하세요.

Comment on lines +50 to +59
/// 앞뒤 짝이 맞는 `"` 또는 `'`만 벗겨낸다. 내부에 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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Suggested change
/// 앞뒤 짝이 맞는 `"` 또는 `'`만 벗겨낸다. 내부에 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

Comment on lines +41 to +50
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
)
])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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:


중복된 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.

Suggested change
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.

Comment on lines +10 to +13
public static func < (lhs: Self, rhs: Self) -> Bool {
let rank: [Self: Int] = [.low: 0, .medium: 1, .high: 2]
return rank[lhs]! < rank[rhs]!
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Suggested change
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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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}')
PY

Repository: 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)}')
PY

Repository: 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:


AMQPS 기본 포트를 5671로 교정하세요.

amqpsdefaultPort가 현재 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.

Comment on lines +73 to +74
.init(prefix: "key-", minLength: 36,
service: "Mailgun", displayLabel: "Mailgun API Key", confidence: .medium),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Mailgun 정규식 규칙이 이 prefix 규칙에 가려져 도달하지 못합니다.

key- prefix 규칙(minLength 36, confidence .medium)은 BuiltInRegexRules.swiftkey-[a-f0-9]{32} 정규식(confidence .high)과 같은 서비스(Mailgun)를 대상으로 합니다. 정확한 Mailgun 키 형식(총 36자)은 이 prefix 조건(hasPrefix("key-") && count >= 36)을 항상 만족합니다.

PrefixRegexDetector.detectprefixMatches가 있으면 정규식 검사 전에 즉시 반환합니다. 결과적으로 정확한 형식을 검증하는 고신뢰도(.high) 정규식 규칙은 절대 실행되지 않고, 실제 Mailgun 키는 항상 .medium confidence로만 분류됩니다. PrefixRegexDetectorTests.swiftmailgunRegex 테스트(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.

Comment on lines +31 to +33
public func redactedPrefix(_ n: Int = 4) -> String {
withUnsafeAccess { "\($0.prefix(n))*** (len=\($0.count))" }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

Suggested change
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.

Comment on lines +27 to +46
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)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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"
fi

Repository: 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가
누적되지 않도록 보장하세요.

@dlguszoo dlguszoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keeep goinggg

@doyeonk429
doyeonk429 merged commit 2f77224 into develop Aug 5, 2026
1 check passed
@doyeonk429
doyeonk429 deleted the feature/#63 branch August 5, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: 시크릿 자동 감지 로직 (Repository + UseCase)

2 participants