Catch more secure fields: role description and non-password secrets#516
Conversation
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.
| 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" | ||
| ] |
There was a problem hiding this comment.
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.
| 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" | |
| ] |
| func test_isSecure_detectsNonPasswordSecretsByLabel() { | ||
| for label in ["CVV", "Security code", "Verification code", "One-time code", "Card number"] { |
There was a problem hiding this comment.
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.
| 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"] { |
Summary
Strengthens the secure-field gate so Cotabby suppresses suggestions in more sensitive fields. The
previous inline check read only role / subrole /
AXDescription/AXTitlefor the substrings"secure" or "password", which missed (1) a native
NSSecureTextField, whose sensitivity is announcedthrough 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
SecureFieldDetectorand the resolvernow also reads
AXRoleDescription.Validation
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
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).
AXRoleDescriptionread per secure-field check on the focus-resolve path; bounded by theexisting 50 ms AX-messaging timeout in
AXHelper.Greptile Summary
This PR extracts the inline secure-field detection into a dedicated
SecureFieldDetectorenum and extends its coverage to include theAXRoleDescriptionattribute (catchingNSSecureTextFieldthat 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.sensitiveMarkerslist 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
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 endReviews (1): Last reviewed commit: "Catch more secure fields: role descripti..." | Re-trigger Greptile