Skip to content

Conversation

@james-d-elliott
Copy link
Member

@james-d-elliott james-d-elliott commented Aug 9, 2025

This enables matching native application origins using simple string comparison. Effective web origins are still matched using case-insensitive matches provided they are valid web origins per HTML5 Section 5.3 (https://www.w3.org/TR/2011/WD-html5-20110525/origin-0.html) and/or simple string comparison per RFC3986 Section 6.2.1 (https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.1). It is important to note that this function completely ignores Apple Associated Domains entirely as Apple is using an unassigned Well-Known URI in breech of Well-Known Uniform Resource Identifiers Section 3 (https://datatracker.ietf.org/doc/html/rfc8615#section-3).

Closes #462, Closes #463

@james-d-elliott james-d-elliott requested a review from a team as a code owner August 9, 2025 13:08
@coderabbitai
Copy link

coderabbitai bot commented Aug 9, 2025

Walkthrough

The verification logic for the Origin and TopOrigin fields in the WebAuthn protocol client code was refactored. A new helper function, IsOriginInHaystack, now checks if a given origin matches any allowed origins, supporting non-FQDN origins and simplifying the control flow. Error handling and comparison logic were updated accordingly. Comprehensive unit tests for the new helper function were added. Additionally, configuration validation was corrected and clarified, with new tests covering configuration scenarios.

Changes

Cohort / File(s) Change Summary
Origin Verification Refactor
protocol/client.go
Introduced IsOriginInHaystack helper with detailed URL component comparison, replaced previous origin verification logic, removed FQDN checks, and updated error handling for Origin and TopOrigin. Added helper functions for URL equality and parsing.
Origin Verification Tests
protocol/client_test.go
Modified setupCollectedClientData to support topOrigin and crossOrigin. Added multiple tests for CollectedClientData.Verify covering various origin and top origin verification modes and error cases. Added TestIsOriginInHaystack with extensive origin matching scenarios.
Configuration Validation Fix and Tests
webauthn/types.go, webauthn/types_test.go
Clarified comments on RPOrigins and RPTopOrigins matching semantics. Fixed validate() method to use named error return and corrected validation logic for RPTopOriginVerificationMode. Added TestNew to verify configuration validation and error handling scenarios.
Protocol Test Assertion Improvement
protocol/func_test.go
Updated AssertIsProtocolError to accept string or *regexp.Regexp for error type, details, and info, enabling flexible assertions with exact or regex matching.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Remove FQDN requirement for origin validation, support non-FQDN origins (#462, #463)
Loosen restrictions on origin to enable native app support (#462, #463)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Possibly related PRs

Poem

🐇 A hop, a skip, origins now align,
No FQDN chains to confine.
Native apps join the WebAuthn dance,
With checks refined, they get their chance.
A rabbit’s cheer for code so bright—
Secure and smooth, a shining light! ✨🐰

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-origin-native

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
protocol/client.go (1)

145-149: topOrigin: uninitialized variable in error path and wrong slice formatting; also subject to raw-string equality

  • fqTopOrigin is never assigned, so the error always reports an empty Received value.
  • %s is incorrect for []string; use %v (or strings.Join).
  • Raw-string comparison shares the same normalization issue as Origin.

Minimal fix for the error reporting:

-                return ErrVerification.
-                    WithDetails("Error validating top origin").
-                    WithInfo(fmt.Sprintf("Expected Values: %s, Received: %s", possibleTopOrigins, fqTopOrigin))
+                return ErrVerification.
+                    WithDetails("Error validating top origin").
+                    WithInfo(fmt.Sprintf("Expected Values: %v, Received: %s", possibleTopOrigins, c.TopOrigin))

Additionally, remove the unused fqTopOrigin variable declared above:

// remove 'fqTopOrigin string' from the var block
var (
    // fqTopOrigin string  // <- delete this
    possibleTopOrigins []string
)

Recommend using the normalized comparison in the helper (see Lines 198-216) to keep HTTP(S) semantics intact while supporting native identifiers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed7dcbb and 7ab8e2e.

📒 Files selected for processing (1)
  • protocol/client.go (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
protocol/client.go (3)
protocol/errors.go (1)
  • ErrVerification (63-66)
protocol/client_test.go (5)
  • TestVerifyCollectedClientDataUnexpectedOrigin (45-58)
  • TestVerifyCollectedClientDataWithMultipleExpectedOrigins (60-75)
  • TestVerifyCollectedClientData (20-29)
  • TestFullyQualifiedOrigin (77-108)
  • setupCollectedClientData (10-18)
webauthn/types.go (1)
  • GetTopOriginVerificationMode (155-157)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (2)
protocol/client.go (2)

129-133: Remove unused fqTopOrigin var

The variable fqTopOrigin is declared but never set; it only leaks into error messages as an empty string.

-            var (
-                fqTopOrigin        string
-                possibleTopOrigins []string
-            )
+            var possibleTopOrigins []string

145-149: Fix incorrect error details for topOrigin and slice formatting

  • Received should report c.TopOrigin (not the unused fqTopOrigin).
  • Use strings.Join (or %v) to format the slice.
-            if !IsOriginInHaystack(c.TopOrigin, possibleTopOrigins) {
+            if !IsOriginInHaystack(c.TopOrigin, possibleTopOrigins) {
                 return ErrVerification.
                     WithDetails("Error validating top origin").
-                    WithInfo(fmt.Sprintf("Expected Values: %s, Received: %s", possibleTopOrigins, fqTopOrigin))
+                    WithInfo(fmt.Sprintf("Expected Values: %s, Received: %s", strings.Join(possibleTopOrigins, ", "), c.TopOrigin))
             }
♻️ Duplicate comments (2)
protocol/client.go (2)

112-116: Fix log formatting for slices and keep messages readable

Use strings.Join (or %v) for []string; %s prints the slice’s pointer. Also keep the received value as-is.

-            WithInfo(fmt.Sprintf("Expected Values: %s, Received: %s", rpOrigins, c.Origin))
+            WithInfo(fmt.Sprintf("Expected Values: %s, Received: %s", strings.Join(rpOrigins, ", "), c.Origin))

198-233: Restore correct “origin” semantics: compare scheme://host[:port] only; ignore path/query/fragment/userinfo

Current logic equates entire URLs (path, query, fragment, userinfo), which regresses from HTML origin semantics and prior FullyQualifiedOrigin behavior. Origin per HTML is scheme://host[:port]. For native identifiers, support exact-string matching.

-// IsOriginInHaystack checks if the needle is in the haystack using the mechanism to determine origin equality defined
-// in HTML5 Section 5.3 and RFC3986 Section 6.2.1.
-//
-// Specifically if the needle value includes the '://' character sequence and can be parsed as URL's;
-// we check each item in the haystack to see if it matches the same rules, and then if the scheme and host components
-// match case-insensitively and check all other components with simple string comparison. The query arguments have a
-// special match condition where we check if the values are equal in length, then iteratively check if each key value
-// pair are matches. This allows the query arguments to have a different order.
-//
-// If the needle value does not include the '://' character sequence or can't be parsed as a URL equality is determined
-// using simple string comparison.
-//
-// See (Origin Definition): https://www.w3.org/TR/2011/WD-html5-20110525/origin-0.html
-//
-// See (Simple String Comparison Definition): https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.1
-func IsOriginInHaystack(needle string, haystack []string) bool {
-    needleURI := parseNormalizedURI(needle)
-
-    if needleURI != nil {
-        for _, hay := range haystack {
-            if hayURI := parseNormalizedURI(hay); hayURI != nil {
-                if isOriginEqual(needleURI, hayURI) {
-                    return true
-                }
-            }
-        }
-    } else {
-        for _, hay := range haystack {
-            if needle == hay {
-                return true
-            }
-        }
-    }
-
-    return false
-}
+// IsOriginInHaystack checks whether needle matches any allowed origins.
+// Web origins (http/https) are compared using FullyQualifiedOrigin (scheme://host[:port]) case-insensitively.
+// Non-web/native identifiers fall back to exact string equality.
+func IsOriginInHaystack(needle string, haystack []string) bool {
+    nFQ, nErr := FullyQualifiedOrigin(needle)
+    nWeb := nErr == nil && strings.Contains(nFQ, "://")
+
+    for _, hay := range haystack {
+        hFQ, hErr := FullyQualifiedOrigin(hay)
+        hWeb := hErr == nil && strings.Contains(hFQ, "://")
+
+        // Compare normalized web origins (ignore path/query/fragment/userinfo)
+        if nWeb && hWeb {
+            if strings.EqualFold(nFQ, hFQ) {
+                return true
+            }
+            continue
+        }
+
+        // Fallback for native identifiers or non-web tokens
+        if needle == hay {
+            return true
+        }
+    }
+    return false
+}

If you prefer, I can follow up with a clean-up patch removing the now-unneeded helpers below.

🧹 Nitpick comments (2)
protocol/client.go (1)

235-306: Remove over-complicated and incorrect URL component comparators

  • isOriginEqual enforces path/query/fragment/userinfo equality, which is not part of origin semantics.
  • isURLValuesEqual uses Values.Get and loses multi-value semantics.
  • parseNormalizedURI ignores parse errors and may proceed with partial URLs.

These are now unnecessary if IsOriginInHaystack uses FullyQualifiedOrigin.

-// remove isOriginEqual, isURLUserEqual, isURLValuesEqual, parseNormalizedURI helpers
-// (entire blocks from lines 235-306 and 308-331)

If you want to keep a helper, retain a minimal isOriginEqual that only compares scheme+host (incl. port) case-insensitively.

Also applies to: 308-331

protocol/client_test.go (1)

208-216: Quiet gitleaks false positives for test fixtures

The android:apk-key-hash strings are test fixtures, not secrets. Add an allow rule or inline allow comment to avoid noisy scans.

Options:

  • Inline comment before the block:
// gitleaks:allow test fixtures for android key hashes (non-secret)
  • Or add an allowlist rule in the repo’s gitleaks config to ignore these test paths/patterns.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ab8e2e and 9c9d7d8.

📒 Files selected for processing (2)
  • protocol/client.go (3 hunks)
  • protocol/client_test.go (1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.27.2)
protocol/client_test.go

209-209: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


210-210: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


215-215: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


216-216: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
protocol/client_test.go (1)

220-229: Rename misnamed tests: these are userinfo cases, not non-FQDN

These cases validate ignoring userinfo, not FQDN-ness. This mirrors earlier feedback.

Apply this minimal rename for clarity:

-            "ShouldHandleNonFQDNOrigin",
+            "ShouldIgnoreUserInfoForOriginComparison",
@@
-            "ShouldHandleNonFQDNOriginExactStringMatch",
+            "ShouldHandleUserInfoExactStringMatch",
🧹 Nitpick comments (3)
protocol/client_test.go (3)

148-152: Confirm default-port equivalence semantics for origins

Many implementations consider https://host and https://host:443 same-origin (likewise http:80). Your test asserts inequality. If your intent is spec-aligned equivalence, add coverage and adjust isOriginEqual accordingly.

Proposed additional tests (if equivalence is desired):

         {
             "ShouldHandleFullyQualifiedOriginWithPort",
             "https://app.example.com:443",
             []string{"https://app.example.com:443"},
             true,
         },
+        {
+            "ShouldTreatDefaultHTTPSPortAsEqual",
+            "https://app.example.com",
+            []string{"https://app.example.com:443"},
+            true,
+        },
+        {
+            "ShouldTreatDefaultHTTPPortAsEqual",
+            "http://app.example.com:80",
+            []string{"http://app.example.com"},
+            true,
+        },
         {
             "ShouldHandleFullyQualifiedOriginDifferentScheme",
             "http://app.example.com",
             []string{"https://app.example.com"},
             false,
         },

If inequality is intentional, consider adding a brief comment in code/tests to document that non-normalized default ports are considered different. Would you like me to open a follow-up to codify this?


196-200: Add empty-slice haystack case

You cover nil haystack; also add []string{} to avoid nil-vs-empty edge regressions.

         {
             "ShouldHandleFullyQualifiedOriginWithoutAllowed",
             "https://app.example.com",
             nil,
             false,
         },
+        {
+            "ShouldHandleFullyQualifiedOriginWithEmptyAllowed",
+            "https://app.example.com",
+            []string{},
+            false,
+        },

117-231: Optional: add an iOS native-origin sample for parity

To complement Android, consider adding an iOS-style origin example to document exact-match behavior for other native formats as well.

         {
             "ShouldHandleNativeAppAndroid",
             "android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0", // gitleaks:allow test fixture
             []string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"}, // gitleaks:allow test fixture
             true,
         },
+        {
+            "ShouldHandleNativeAppIOS",
+            "ios:bundle-id:com.example.app",
+            []string{"ios:bundle-id:com.example.app"},
+            true,
+        },
+        {
+            "ShouldHandleNativeAppIOSCaseSensitive",
+            "ios:bundle-id:com.example.App",
+            []string{"ios:bundle-id:com.example.app"},
+            false,
+        },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c9d7d8 and e11cd00.

📒 Files selected for processing (2)
  • protocol/client.go (3 hunks)
  • protocol/client_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • protocol/client.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
protocol/client_test.go (1)
protocol/client.go (1)
  • IsOriginInHaystack (211-231)
🪛 Gitleaks (8.27.2)
protocol/client_test.go

209-209: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


210-210: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


215-215: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


216-216: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
protocol/client_test.go (3)

154-170: Correctly ignore path and query for web-origin comparison

Good alignment with HTML origin semantics. Treating differing paths and queries as equal origins is correct.


190-194: Correctly ignore fragment for web-origin comparison

Fragments are not origin-significant; expecting equality here is correct.


208-218: Silence Gitleaks false positives on test android hash strings

CI scanners flag these as generic API keys. Mark them as test fixtures inline to avoid noise without weakening coverage.

         {
             "ShouldHandleNativeAppAndroid",
-            "android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0",
-            []string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"},
+            "android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0", // gitleaks:allow test fixture
+            []string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"}, // gitleaks:allow test fixture
             true,
         },
         {
             "ShouldHandleNativeAppAndroidCaseSensitive",
-            "android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69F0",
-            []string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"},
+            "android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69F0", // gitleaks:allow test fixture
+            []string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"}, // gitleaks:allow test fixture
             false,
         },

Alternatively, we can change the hex payload to a clearly fake value (e.g., 40 'a's) and add a brief comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
webauthn/types_test.go (1)

49-55: Avoid asserting full parse error text

The expected string for bad RPID includes the exact parse message from net/url. Even with the loop change above, consider trimming tc.err to a stable substring like "field 'RPID' is not a valid URI".

protocol/client_test.go (3)

196-200: Add coverage for empty allowed origins (not just nil)

You cover nil; also cover empty slice to ensure behavior is consistent.

         {
             "ShouldHandleFullyQualifiedOriginWithoutAllowed",
             "https://app.example.com",
             nil,
             false,
         },
+        {
+            "ShouldHandleFullyQualifiedOriginWithEmptyAllowed",
+            "https://app.example.com",
+            []string{},
+            false,
+        },

219-231: Rename misleading test names (they’re FQDNs with userinfo, not non-FQDN)

Clarify names to reflect userinfo-ignoring behavior and exact-match variant.

-        {
-            "ShouldHandleNonFQDNOrigin",
+        {
+            "ShouldIgnoreUserInfoForOriginComparison",
             "https://user:[email protected]/",
             []string{"https://app.example.com/"},
             true,
         },
-        {
-            "ShouldHandleNonFQDNOriginExactStringMatch",
+        {
+            "ShouldHandleFullyQualifiedOriginExactMatchWithCredentials",
             "https://user:[email protected]/",
             []string{"https://user:[email protected]/"},
             true,
         },

209-216: Gitleaks false positive: allowlist Android key-hash test constants

The Android apk key-hash values trigger generic key detection. They are test constants, not secrets. Consider a repo-level gitleaks allowlist.

Example gitleaks config snippet:

# .gitleaks.toml
[allowlist]
  description = "Allowlist android apk key-hash test constants"
  regexes = [
    '''android:apk-key-hash:[0-9a-f]{40}'''
  ]

Alternatively, split the hash in tests (e.g., join parts at runtime) to avoid matching.

webauthn/types.go (2)

36-41: Clarify origin matching semantics (port normalization; ignore path/query/fragment/userinfo)

Make the comment explicit about:

  • For http/https: compare scheme and host case-insensitively AND port (normalizing defaults), ignoring path, query, fragment, and userinfo.
  • Otherwise: exact string comparison.
-// RPOrigins configures the list of Relying Party Server Origins that are permitted. The provided origins can either
-// be fully qualified origins or strings for simple string comparison. The strings are matched using canonical
-// origin matching semantics specifically if they start with 'http://' or 'https://' if the provided origin has a
-// case-insensitive equal scheme and host component they are equal, otherwise simple string comparison is utilized
-// to determine equality.
+// RPOrigins configures the list of permitted RP origins. Entries may be fully-qualified web origins or arbitrary
+// strings. Matching semantics:
+// - For http/https: compare scheme and host case-insensitively and compare ports with default normalization
+//   (https→443, http→80). Ignore path, query, fragment, and userinfo.
+// - For all other schemes: use exact, case-sensitive string comparison.

43-48: Mirror the clarified semantics for RPTopOrigins

Keep RPTopOrigins documentation consistent with RPOrigins.

-// RPTopOrigins configures the list of Relying Party Server Top Origins that are permitted. The provided origins can
-// either be fully qualified origins or strings for simple string comparison. The strings are matched using
-// canonical origin matching semantics specifically if they start with 'http://' or 'https://' if the provided
-// origin has a case-insensitive equal scheme and host component they are equal, otherwise simple string comparison
-// is utilized to determine equality.
+// RPTopOrigins configures the list of permitted RP top-level origins. Semantics are identical to RPOrigins:
+// - For http/https: scheme+host case-insensitive, port compared with default normalization; path, query,
+//   fragment, userinfo ignored.
+// - For other schemes: exact, case-sensitive string comparison.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0628c40 and 85e2bba.

📒 Files selected for processing (4)
  • protocol/client.go (3 hunks)
  • protocol/client_test.go (1 hunks)
  • webauthn/types.go (3 hunks)
  • webauthn/types_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • protocol/client.go
🧰 Additional context used
🪛 Gitleaks (8.27.2)
protocol/client_test.go

209-209: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


210-210: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


215-215: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


216-216: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (4)
webauthn/types_test.go (1)

3-9: Imports look good

The added imports are appropriate for the new tests.

protocol/client_test.go (2)

154-194: Good alignment with web origin semantics (path/query/fragment/userinfo ignored)

These cases correctly ignore path, query, fragment, and userinfo for HTTP(S) origins. This matches HTML origin semantics.


208-218: LGTM on native app exact matching

Case-sensitive exact string comparison for non-HTTP(S) origins (Android) is appropriate.

webauthn/types.go (1)

133-140: Correct fix for explicit top-origin verification mode

Validating that RPTopOrigins is non-empty when RPTopOriginVerificationMode is set to Explicit is correct.

@james-d-elliott james-d-elliott force-pushed the feat-origin-native branch 3 times, most recently from 2f5e852 to c9d252a Compare August 10, 2025 04:56
This enables matching native application origins using simple string comparison. Effective web origins are still matched using case-insensitive matches provided they are valid web origins per HTML5 Section 5.3 (https://www.w3.org/TR/2011/WD-html5-20110525/origin-0.html) and/or simple string comparison per RFC3986 Section 6.2.1 (https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.1). It is important to note that this function completely ignores Apple Associated Domains entirely as Apple is using an unassigned Well-Known URI in breech of Well-Known Uniform Resource Identifiers Section 3 (https://datatracker.ietf.org/doc/html/rfc8615#section-3).

Closes #462, Closes #463
@codecov
Copy link

codecov bot commented Aug 10, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 43.82%. Comparing base (b03e544) to head (4e07156).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #468      +/-   ##
==========================================
+ Coverage   41.62%   43.82%   +2.20%     
==========================================
  Files          34       34              
  Lines        3063     3087      +24     
==========================================
+ Hits         1275     1353      +78     
+ Misses       1610     1561      -49     
+ Partials      178      173       -5     
Files with missing lines Coverage Δ
protocol/client.go 89.83% <100.00%> (+47.43%) ⬆️
webauthn/types.go 81.13% <100.00%> (+22.95%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
protocol/client_test.go (2)

226-236: Rename misnamed tests: these validate userinfo handling, not “NonFQDN”

The names suggest non-FQDN, but the URLs are FQDNs with userinfo. Rename for clarity.

-        {
-            "ShouldHandleNonFQDNOrigin",
+        {
+            "ShouldIgnoreUserInfoForOriginComparison",
             "https://user:[email protected]/",
             []string{"https://app.example.com/"},
             true,
         },
-        {
-            "ShouldHandleNonFQDNOriginExactStringMatch",
+        {
+            "ShouldHandleUserInfoExactStringMatch",
             "https://user:[email protected]/",
             []string{"https://user:[email protected]/"},
             true,
         },

148-152: Fix “DifferentPort” test to actually test a non-default port (avoid duplicate with default-port cases)

This test currently duplicates the default-port equivalence (also covered at Lines 238-242). Make it a non-default port to assert inequality.

-        {
-            "ShouldHandleFullyQualifiedOriginDifferentPort",
-            "https://app.example.com:443",
-            []string{"https://app.example.com"},
-            true,
-        },
+        {
+            "ShouldHandleFullyQualifiedOriginDifferentPort",
+            "https://app.example.com:8443",
+            []string{"https://app.example.com"},
+            false,
+        },
🧹 Nitpick comments (2)
protocol/client_test.go (2)

214-224: Silence gitleaks false positives in test data (use obvious non-secrets or allowlist)

The android:apk-key-hash values trigger “Generic API Key” findings. Since these are fixtures, prefer obviously non-secret placeholders to keep scanners quiet.

         {
             "ShouldHandleNativeAppAndroid",
-            "android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0",
-            []string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"},
+            "android:apk-key-hash:exampleapkkeyhash",
+            []string{"android:apk-key-hash:exampleapkkeyhash"},
             true,
         },
         {
             "ShouldHandleNativeAppAndroidCaseSensitive",
-            "android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69F0",
-            []string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"},
+            "android:apk-key-hash:EXAMPLEAPKKEYHASH",
+            []string{"android:apk-key-hash:exampleapkkeyhash"},
             false,
         },

Alternatively, add an allowlist entry in the gitleaks config for these known-safe test constants.


249-249: Optional: Add a few more cases to tighten coverage (iOS native, empty haystack, reverse default-port)

  • iOS native origins should be simple string-compare like Android; add positive and case-sensitive negative.
  • Test empty haystack (distinct from nil).
  • Add reverse direction for default-port equivalence.

Example additions to the table:

{
    "ShouldHandleNativeAppiOS",
    "ios:bundle-id:com.example.app",
    []string{"ios:bundle-id:com.example.app"},
    true,
},
{
    "ShouldHandleNativeAppiOSCaseSensitive",
    "ios:bundle-id:com.Example.App",
    []string{"ios:bundle-id:com.example.app"},
    false,
},
{
    "ShouldHandleEmptyHaystack",
    "https://app.example.com",
    []string{},
    false,
},
{
    "ShouldHandleFullyQualifiedOriginDefaultPortEquivalentHTTPSReverse",
    "https://app.example.com",
    []string{"https://app.example.com:443"},
    true,
},
{
    "ShouldHandleFullyQualifiedOriginDefaultPortEquivalentHTTPReverse",
    "http://app.example.com",
    []string{"http://app.example.com:80"},
    true,
},

If helpful, I can push a patch updating the table and names.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85e2bba and 49d0578.

📒 Files selected for processing (4)
  • protocol/client.go (3 hunks)
  • protocol/client_test.go (1 hunks)
  • webauthn/types.go (3 hunks)
  • webauthn/types_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • webauthn/types_test.go
  • webauthn/types.go
  • protocol/client.go
🧰 Additional context used
🪛 Gitleaks (8.27.2)
protocol/client_test.go

215-215: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


216-216: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


221-221: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


222-222: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (1)
protocol/client_test.go (1)

110-249: Good coverage and alignment with HTML origin semantics

The new table-driven tests for IsOriginInHaystack comprehensively cover scheme/host case-insensitivity, default port normalization, and ignoring path/query/fragment/userinfo for web origins. Nice job.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
protocol/client_test.go (2)

204-356: LGTM: Test coverage aligns with origin semantics (scheme/host/port only) and native app exact-match

The new IsOriginInHaystack tests correctly ignore path/query/fragment/userinfo for web origins, normalize default ports, and treat native app origins as exact case-sensitive strings. This addresses prior review feedback.


320-324: Rename test for clarity: it’s about userinfo, not non-FQDN

The host is an FQDN; the test asserts userinfo is ignored. Rename to reflect behavior.

-			"ShouldHandleNonFQDNOrigin",
+			"ShouldIgnoreUserInfoForOriginComparison",
🧹 Nitpick comments (10)
protocol/func_test.go (3)

12-12: Mark helper to improve failure locations

Call t.Helper() so failures report the caller’s line, not inside this helper.

-func AssertIsProtocolError(t *testing.T, err error, errType, errDetails, errInfo any) {
+func AssertIsProtocolError(t *testing.T, err error, errType, errDetails, errInfo any) {
+	t.Helper()

17-24: Allow nil to skip specific assertions and reduce brittleness

Permitting nil for errType/errDetails/errInfo lets tests opt-out of individual checks without crafting placeholder values. Add a nil case in each type-switch.

- switch et := errType.(type) {
+ switch et := errType.(type) {
+ case nil:
+ 	// skip
  case string:
    assert.Equal(t, et, e.Type)
  case *regexp.Regexp:
    assert.Regexp(t, et, e.Type)
  default:
    t.Fatalf("%T is not a known type", errType)
  }
 
- switch ed := errDetails.(type) {
+ switch ed := errDetails.(type) {
+ case nil:
+ 	// skip
  case string:
    assert.Equal(t, ed, e.Details)
  case *regexp.Regexp:
    assert.Regexp(t, ed, e.Details)
  default:
    t.Fatalf("%T is not a known type", errDetails)
  }
 
- switch ed := errInfo.(type) {
+ switch ed := errInfo.(type) {
+ case nil:
+ 	// skip
  case string:
    assert.Equal(t, ed, e.DevInfo)
  case *regexp.Regexp:
    assert.Regexp(t, ed, e.DevInfo)
  default:
    t.Fatalf("%T is not a known type", errInfo)
  }

Also applies to: 26-33, 35-42


23-24: Clarify fatal messages

Include the parameter name and allowed types to speed up failures diagnosis.

-    t.Fatalf("%T is not a known type", errType)
+    t.Fatalf("errType: %T is not a supported type (allowed: string, *regexp.Regexp, nil)", errType)
...
-    t.Fatalf("%T is not a known type", errDetails)
+    t.Fatalf("errDetails: %T is not a supported type (allowed: string, *regexp.Regexp, nil)", errDetails)
...
-    t.Fatalf("%T is not a known type", errInfo)
+    t.Fatalf("errInfo: %T is not a supported type (allowed: string, *regexp.Regexp, nil)", errInfo)

Also applies to: 32-33, 41-42

protocol/client_test.go (7)

3-9: Prefer regexp over fmt string for dev info assertions; update imports

You’re using fmt only to build an exact dev-info string which is brittle. Switch to regexp and drop fmt.

-import (
-	"fmt"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
+import (
+	"regexp"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)

42-42: Avoid passing []string{""} as allowed top origins when topOrigin is empty

Passing a single empty string can be interpreted as “one allowed origin that is empty”. Prefer nil to state “no allowed top origins provided” in explicit mode, or switch the mode to Implicit/Ignore depending on intent.

-	require.NoError(t, ccd.Verify(storedChallenge.String(), ccd.Type, []string{ccd.Origin}, []string{ccd.TopOrigin}, TopOriginExplicitVerificationMode))
+	require.NoError(t, ccd.Verify(storedChallenge.String(), ccd.Type, []string{ccd.Origin}, nil, TopOriginExplicitVerificationMode))

86-86: TopOrigin auto mode: align schemes to avoid false positives

In Auto mode, if top origins are consulted, http vs https should not match. Either ensure the implementation ignores the provided top origins here or align the test data’s scheme to match the actual topOrigin.

-	require.NoError(t, ccd.Verify(storedChallenge.String(), ccd.Type, []string{ccd.Origin}, []string{"https://example.com"}, TopOriginAutoVerificationMode))
+	require.NoError(t, ccd.Verify(storedChallenge.String(), ccd.Type, []string{ccd.Origin}, []string{"http://example.com"}, TopOriginAutoVerificationMode))

111-111: Use a regexp for dev-info to reduce brittleness

The debug text format may change; assert shape instead of exact string.

-	AssertIsProtocolError(t, ccd.Verify(bogusChallenge.String(), ccd.Type, []string{ccd.Origin}, []string{ccd.TopOrigin}, TopOriginExplicitVerificationMode), "verification_error", "Error validating challenge", fmt.Sprintf("Expected b Value: \"%s\"\nReceived b: \"%s\"\n", bogusChallenge.String(), newChallenge.String()))
+	re := regexp.MustCompile(`^Expected b Value: ".+"\nReceived b: ".+"\n$`)
+	AssertIsProtocolError(t, ccd.Verify(bogusChallenge.String(), ccd.Type, []string{ccd.Origin}, []string{ccd.TopOrigin}, TopOriginExplicitVerificationMode), "verification_error", "Error validating challenge", re)

149-151: Be consistent and precise with error assertions

Use AssertIsProtocolError to document the expected type/details and verify debug info shape. This prevents silent acceptance of wrong error types/messages.

-	if err = ccd.Verify(storedChallenge.String(), ccd.Type, []string{ccd.TopOrigin}, expectedOrigins, TopOriginExplicitVerificationMode); err == nil {
-		t.Fatalf("error expected but not received. expected %#v got %#v", expectedOrigins, ccd.Origin)
-	}
+	err = ccd.Verify(storedChallenge.String(), ccd.Type, []string{ccd.TopOrigin}, expectedOrigins, TopOriginExplicitVerificationMode)
+	AssertIsProtocolError(t, err, "verification_error", "Error validating topOrigin", regexp.MustCompile(`(?s)Expected.*Received.*`))

326-330: Rename test for clarity: exact-match with userinfo

Clarify that this case expects exact string matching including userinfo.

-			"ShouldHandleNonFQDNOriginExactStringMatch",
+			"ShouldMatchUserInfoWithExactStringMatch",

308-316: Gitleaks false-positive: allowlist the android apk-key-hash test data

These literal values trigger “Generic API Key” in Gitleaks. Add an inline allow comment to prevent pipeline noise.

 		{
-			"ShouldHandleNativeAppAndroid",
+			// gitleaks:allow
+			"ShouldHandleNativeAppAndroid",
 			"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0",
 			[]string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"},
 			true,
 		},
 		{
-			"ShouldHandleNativeAppAndroidCaseSensitive",
+			// gitleaks:allow
+			"ShouldHandleNativeAppAndroidCaseSensitive",
 			"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69F0",
 			[]string{"android:apk-key-hash:7d1043473d55bfa90e8530d35801d4e381bc69f0"},
 			false,
 		},

If inline allow isn’t enabled, consider adding these patterns to the repo’s Gitleaks allowlist configuration targeting test files.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 49d0578 and f6011e6.

📒 Files selected for processing (2)
  • protocol/client_test.go (6 hunks)
  • protocol/func_test.go (1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.27.2)
protocol/client_test.go

309-309: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


310-310: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


315-315: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


316-316: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)

@james-d-elliott james-d-elliott merged commit 0b2a549 into master Aug 11, 2025
11 checks passed
@james-d-elliott james-d-elliott deleted the feat-origin-native branch August 11, 2025 22:26
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.

additional support for native devices

2 participants