Skip to content

Catch more secure fields: role description and non-password secrets#516

Merged
FuJacob merged 1 commit into
mainfrom
feat/secure-field-detection
Jun 2, 2026
Merged

Catch more secure fields: role description and non-password secrets#516
FuJacob merged 1 commit into
mainfrom
feat/secure-field-detection

Conversation

@FuJacob

@FuJacob FuJacob commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

Strengthens the secure-field gate so Cotabby suppresses suggestions in more sensitive fields. The
previous inline check read only role / subrole / AXDescription / AXTitle for the substrings
"secure" or "password", which missed (1) a native NSSecureTextField, whose sensitivity is announced
through the role description ("secure text field") rather than the description, and (2) non-password
secrets (CVV, security/verification code, one-time code, card number) that never contain the word
"password". The policy is extracted into a pure, unit-tested SecureFieldDetector and the resolver
now also reads AXRoleDescription.

Validation

xcodebuild ... test ... CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
  -only-testing:CotabbyTests/SecureFieldDetectorTests
# ** TEST SUCCEEDED ** — 7 tests, 0 failures
#   incl. NSSecureTextField-via-role-description, non-password secrets, benign-field negatives

swiftlint lint --quiet   # exit 0 (clean)
xcodegen generate        # registered the two new files (CI drift guard passes)

Linked issues

None. Closes a confirmed gap where the secure-field check missed role-description-only secure fields
and non-password secrets.

Risk / rollout notes

  • Suppression-only change. This can only ever cause Cotabby to decline to suggest in a field it
    previously acted on; it never surfaces a suggestion it previously withheld. The marker set is
    deliberately broad for that reason (a false positive costs a missed suggestion; a false negative
    could surface a secret).
  • One extra AXRoleDescription read per secure-field check on the focus-resolve path; bounded by the
    existing 50 ms AX-messaging timeout in AXHelper.

Greptile Summary

This PR extracts the inline secure-field detection into a dedicated SecureFieldDetector enum and extends its coverage to include the AXRoleDescription attribute (catching NSSecureTextField that announces sensitivity only through its role description) and a broader set of sensitive-field keywords beyond "secure"/"password".

  • SecureFieldDetector.isSecure() is a pure, string-only function with 7 unit tests; the resolver now reads one additional AX attribute (kAXRoleDescriptionAttribute) per focus-resolve call.
  • The sensitiveMarkers list covers card numbers, CVV/CVC, OTC, passcodes, and social security phrases, but is missing several common abbreviated labels ("pin", "ssn", "otp", "secret") that would silently pass a field through as non-sensitive.

Confidence Score: 4/5

Safe to merge as a net security improvement; the only risk is that a few common abbreviated labels remain unblocked, which was also the case before this PR.

The refactoring is clean and suppression-only — it can never cause a previously-hidden secret to surface. The gap in the marker list (missing pin, ssn, otp, secret) means some real sensitive fields still won't be blocked, but this is not a regression introduced by the PR.

Cotabby/Support/SecureFieldDetector.swift — the sensitiveMarkers list is the authoritative policy; any gaps there silently pass sensitive fields through to the suggestion engine.

Important Files Changed

Filename Overview
Cotabby/Support/SecureFieldDetector.swift New pure enum encapsulating secure-field detection policy; well-structured, but the sensitiveMarkers list is missing several common sensitive-field identifiers (PIN, SSN abbreviation, secret) that could silently allow suggestions in sensitive inputs.
Cotabby/Services/Focus/FocusSnapshotResolver.swift isSecureElement refactored to delegate to SecureFieldDetector; correctly adds kAXRoleDescriptionAttribute read to catch NSSecureTextField, functional behavior is equivalent for all previously-covered markers.
CotabbyTests/SecureFieldDetectorTests.swift 7 focused unit tests covering the new role-description path, non-password secrets, case-insensitivity, and benign-field negatives; missing coverage for common abbreviated labels (PIN, SSN, OTP) and the all-nil input edge case.
Cotabby.xcodeproj/project.pbxproj Standard project file update registering SecureFieldDetector.swift in the app target and SecureFieldDetectorTests.swift in the test target; no issues found.

Sequence Diagram

sequenceDiagram
    participant App as FocusSnapshotResolver
    participant AX as AXHelper
    participant Det as SecureFieldDetector

    App->>AX: stringValue(kAXRoleDescriptionAttribute)
    AX-->>App: roleDescription
    App->>AX: stringValue(kAXTitleAttribute)
    AX-->>App: title
    App->>AX: stringValue(kAXDescriptionAttribute)
    AX-->>App: descriptionLabel

    App->>Det: isSecure(role, subrole, roleDescription, title, descriptionLabel)
    Det->>Det: compactMap + filter empty markers
    Det->>Det: check each marker against sensitiveMarkers substrings
    Det-->>App: Bool

    alt "isSecure == true"
        App-->>App: skip suggestion
    else "isSecure == false"
        App-->>App: proceed with suggestion
    end
Loading

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "Catch more secure fields: role descripti..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

The secure-field gate read only role/subrole/AXDescription/AXTitle for 'secure'/'password', missing a native NSSecureTextField (sensitivity is in the role description, 'secure text field') and non-password secrets (CVV, security/verification code, one-time code, card number). Extract the policy into a pure, unit-tested SecureFieldDetector and also read AXRoleDescription.

Suppression-only: this can only make Cotabby decline to suggest in a sensitive field, never surface a withheld suggestion, so the marker set errs toward caution.
@FuJacob FuJacob merged commit b12129d into main Jun 2, 2026
4 checks passed
@FuJacob FuJacob deleted the feat/secure-field-detection branch June 2, 2026 00:56
Comment on lines +41 to +55
static let sensitiveMarkers: [String] = [
"secure",
"password",
"passcode",
"passphrase",
"cvv",
"cvc",
"security code",
"verification code",
"one-time code",
"one time code",
"social security",
"card number",
"credit card"
]

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.

P2 Several common sensitive-field identifiers are missing from sensitiveMarkers. A field whose label or title is exactly "PIN", "SSN", or "OTP" would produce false from isSecure(), allowing Cotabby to suggest into those fields. "pin" in particular is ubiquitous in banking and device-auth flows; "ssn" covers the abbreviation for Social Security Number that "social security" does not catch; "otp" and "secret" are common in API-credential and 2FA flows.

Suggested change
static let sensitiveMarkers: [String] = [
"secure",
"password",
"passcode",
"passphrase",
"cvv",
"cvc",
"security code",
"verification code",
"one-time code",
"one time code",
"social security",
"card number",
"credit card"
]
static let sensitiveMarkers: [String] = [
"secure",
"password",
"passcode",
"passphrase",
"pin",
"cvv",
"cvc",
"otp",
"secret",
"security code",
"verification code",
"one-time code",
"one time code",
"social security",
"ssn",
"card number",
"credit card",
"account number"
]

Fix in Codex Fix in Claude Code

Comment on lines +26 to +27
func test_isSecure_detectsNonPasswordSecretsByLabel() {
for label in ["CVV", "Security code", "Verification code", "One-time code", "Card number"] {

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.

P2 The test for non-password secrets doesn't include abbreviated labels like "PIN" or "SSN", which are present in real banking UIs and absent from the current sensitiveMarkers list. Adding them here would also serve as a regression guard if the marker list is ever pruned.

Suggested change
func test_isSecure_detectsNonPasswordSecretsByLabel() {
for label in ["CVV", "Security code", "Verification code", "One-time code", "Card number"] {
func test_isSecure_detectsNonPasswordSecretsByLabel() {
for label in ["CVV", "Security code", "Verification code", "One-time code", "Card number", "PIN", "SSN", "OTP"] {

Fix in Codex Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant