Skip to content

test(injected): add unit coverage for autofillPasskeys passkey interception - #2738

Open
cursor[bot] wants to merge 4 commits into
mainfrom
jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810
Open

test(injected): add unit coverage for autofillPasskeys passkey interception#2738
cursor[bot] wants to merge 4 commits into
mainfrom
jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810

Conversation

@cursor

@cursor cursor Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Asana Task/Github Issue: Coverage automation — addresses untested autofillPasskeys feature merged in #2571

Description

The autofillPasskeys feature intercepts navigator.credentials.get() for conditional WebAuthn passkey autofill on Windows. It shipped with no unit tests despite complex concurrency, messaging, and WebIDL semantics.

This PR adds focused Jasmine coverage for the highest-risk paths and fixes ESM import suffixes in autofill-passkeys.js so the module loads under the unit test runner (no runtime behavior change).

Risky behavior now covered

  • Request registration: registerPasskeyRequest notifies native with rpId + requestId, subscribes to passkeySelected, and completes via narrowed originalGet with decoded allowCredentials.
  • Concurrency: superseding a pending conditional request rejects the first promise with AbortError; AbortSignal abort/reason handling and already-aborted fast path.
  • Message validation: ignores mismatched requestId, empty/non-string credentialId; rejects invalid base64 instead of hanging.
  • WebIDL fidelity: option getter throws surface as rejected promises, not synchronous throws.
  • Pass-through guards: non-conditional mediation, missing publicKey, non-secure context, and foreign credentials.get receivers do not register passkey requests.

Test files added/updated

  • Added: injected/unit-test/autofill-passkeys.js (16 specs)
  • Updated: injected/src/features/autofill-passkeys.js (.js suffix on ESM imports only)

Why these tests materially reduce regression risk

navigator.credentials.get is a security-sensitive, promise-based API. Regressions in pass-through conditions, abort/supersede handling, or native message correlation can hang site login flows or break WebAuthn ceremonies. These tests lock in the behavior explicitly documented in the #2571 review thread without requiring Windows/WebView2 integration infrastructure.

Testing Steps

  • cd injected && npm run test-unit -- --filter=AutofillPasskeys

Checklist

  • I have tested this change locally
  • I have tested this change locally in all supported browsers
  • This change will be visible to users
  • I have added automated tests that cover this change
  • I have ensured the change is gated by config
  • This change was covered by a ship review
  • This change was covered by a tech design
  • Any dependent config has been merged
Open in Web View Automation 

Note

Low Risk
Test-only coverage plus import path fixes; no intentional changes to passkey interception behavior in production.

Overview
Adds Jasmine unit coverage for the AutofillPasskeys injected feature (conditional WebAuthn passkey interception) and tweaks ESM import paths in autofill-passkeys.js so the module loads under the unit test runner.

The new injected/unit-test/autofill-passkeys.js suite exercises registerPasskeyRequest: native registerPasskeyRequest notify with rpId/requestId, passkeySelected subscription, narrowed originalGet with decoded allowCredentials, rpId fallback to location.hostname when publicKey.rpId is not a string, superseding pending requests, and AbortSignal handling.

It also locks in message validation (wrong requestId, malformed credentialId, invalid base64) and WebIDL-style rejection when option getters throw, plus init wrapper pass-through for non-conditional mediation, missing publicKey, non-secure context, and non-singleton credentials.get receivers versus interception on navigator.credentials.

Production change: only .js suffixes on imports from content-feature and captured-globals; no intended runtime behavior change.

Reviewed by Cursor Bugbot for commit 3449a00. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Build Branch

Branch pr-releases/jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810
Commit cdd64df570
Updated June 12, 2026 at 2:36:02 PM UTC

Static preview entry points

QR codes (mobile preview)
Entry point QR code
Docs QR for docs preview
Static pages QR for static pages preview
Integration pages QR for integration pages preview

Integration commands

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#pr-releases/jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", branch: "pr-releases/jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810
git -C submodules/content-scope-scripts checkout origin/pr-releases/jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810
Pin to exact commit

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#cdd64df570e665a3cbb432a732bf3bf32497d344

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", revision: "cdd64df570e665a3cbb432a732bf3bf32497d344")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810
git -C submodules/content-scope-scripts checkout cdd64df570e665a3cbb432a732bf3bf32497d344

@github-actions github-actions Bot added the semver-patch Bug fix / internal — no release needed label Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

[Beta] Generated file diff

Time updated: Fri, 12 Jun 2026 14:37:12 GMT

Add Jasmine tests for the Windows passkey interception flow merged in
#2571, covering request registration, native message correlation, abort
handling, supersede behavior, and credentials.get pass-through guards.

Fix ESM import paths in autofill-passkeys.js so the feature can be loaded
by the unit test runner (no behavior change).

Co-authored-by: Jonathan Kingston <jonathanKingston@users.noreply.github.com>
@jonathanKingston
jonathanKingston force-pushed the jkt/auto/bc-b6b1f301-fd84-44f9-a038-9278e053ee7a-d810 branch from 26130a6 to 0230491 Compare June 11, 2026 01:38
Comment thread injected/unit-test/autofill-passkeys.js Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale comment

Injected PR Evaluation: Web Compatibility & Security

Reviewed diff 7a9074e..0230491 — production change is import-path normalization only; the bulk of the PR is new unit tests for AutofillPasskeys.

Web Compatibility Assessment

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1, 10 info Import paths gain explicit .js extensions (content-feature.js, captured-globals.js). No runtime behavior change; consistent with other strict-core feature files (web-compat.js, message-bridge.js).
injected/unit-test/autofill-passkeys.js (new) info Tests validate existing pass-through paths that protect compatibility: non-conditional mediation, missing publicKey, non-secure context, foreign CredentialsContainer receiver, and WebIDL promise semantics (throwing option getters reject the returned promise rather than throwing synchronously).

No web-compat regressions identified in the production diff.

Security Assessment

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1, 10 info No security-relevant logic changes.
injected/unit-test/autofill-passkeys.js (new) info Tests exercise existing security boundaries: rpId falls back to location.hostname when not a string (blocks object-coercion attacks), requestId correlation prevents cross-request credential injection, malformed/empty credentialId values are ignored, invalid base64 rejects. Outbound notify payloads use explicit { rpId, requestId } — no nativeData spread risk.

No new security vulnerabilities introduced.

Risk Level

Low Risk — test-only coverage addition plus cosmetic import-path normalization; no changes to API overrides, messaging transports, captured globals, or initialization order.

Recommendations

  1. (info) All 16 new specs pass locally. No blocking items.
  2. (info) The tests are a solid regression harness for the existing registerPasskeyRequest / credentials.get wrapper contract. Consider adding an integration test (Playwright) in a follow-up if end-to-end native messaging isn't already covered elsewhere — not required for this PR.
  3. (info) The .js import extension change is safe for the bundler pipeline; no action needed.

Verdict: Approve from web-compat and security perspective.

Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

@jonathanKingston
jonathanKingston marked this pull request as ready for review June 11, 2026 10:49

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale comment

Injected PR Evaluation: Web Compatibility & Security

Web Compatibility Assessment

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info Only change is adding .js extensions to static imports (content-feature.js, captured-globals.js). This is a bundler/ESM path normalization with no runtime behavior change. Mixed extension usage exists elsewhere in the repo; this aligns with newer feature files.
injected/unit-test/autofill-passkeys.js (all) info Test-only file. No injected page-world code ships from this diff.
injected/unit-test/autofill-passkeys.js 377–473 info init credentials.get wrapper tests document important pass-through guards already in production: non-conditional mediation, missing publicKey, non-secure context, and foreign this receiver. These are the primary web-compat safety rails for the interception path.

No web-compatibility regressions identified in production code.

Security Assessment

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info No logic, messaging, or API-wrapping changes. Security posture of the live feature is unchanged.
injected/unit-test/autofill-passkeys.js 200–219 info Test confirms rpId is only taken from publicKey.rpId when typeof === 'string', rejecting object coercion via toString() — matches the production guard at line 95.
injected/unit-test/autofill-passkeys.js 305–337 info Tests verify requestId correlation and malformed credentialId rejection, covering the inbound message trust boundary exercised by passkeySelected subscription.
injected/unit-test/autofill-passkeys.js 187–191, 446–449 info Assertions confirm outbound notify('registerPasskeyRequest', { rpId, requestId }) uses an explicit field list — no object spread that could leak nativeData.
injected/unit-test/autofill-passkeys.js info Coverage gap (not a regression): no test for init() early-return when CredentialsContainer / navigator.credentials is absent, and no test for the randomUUID unavailable fallback path (lines 77–80). These are follow-up opportunities, not blockers for this PR.

No security vulnerabilities introduced by this diff.

Risk Level

Low Risk — the diff adds unit-test coverage and normalizes import paths; it does not modify API override logic, messaging transports, captured globals, or wrapper utilities.

Recommendations

  1. (info) Consider follow-up tests for init() bail-out when the Credentials API is missing, and for the randomUUID fallback that delegates to originalGet.
  2. (info) If native ever delivers credential IDs as base64url (common in WebAuthn), consider a follow-up test + feature check — current atob() path only handles standard base64 (partially exercised by the invalid-base64 rejection test).
  3. Approve — this PR improves confidence in existing passkey interception behavior without changing production semantics.

Cursor Automation — Web Compatibility & Security review

Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale comment

Web Compatibility & Security Review

Web Compatibility Assessment

No web compatibility regressions identified.

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info Production diff is import-path normalization only (content-feature.js, captured-globals.js). No runtime behavior change.
injected/unit-test/autofill-passkeys.js 377–473 info New tests document and lock in correct pass-through behavior: non-conditional mediation, missing publicKey, non-secure contexts, and foreign CredentialsContainer receivers. These guard against accidental broadening of the credentials.get interception.
injected/unit-test/autofill-passkeys.js 456–472 info Tests confirm WebIDL promise semantics: option-getter throws surface as rejected promises, not synchronous throws — matching native get() contract.

Security Assessment

No security vulnerabilities introduced.

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info No changes to messaging, API overrides, or captured-globals usage.
injected/unit-test/autofill-passkeys.js 200–219 info Test validates rpId is narrowed via typeof === 'string' check, rejecting page-controlled toString() coercion — documents an existing security property of the feature.
injected/unit-test/autofill-passkeys.js 305–318 info Test validates requestId matching on inbound passkeySelected messages, preventing cross-request credential injection.
injected/unit-test/autofill-passkeys.js 321–354 info Tests cover malformed/invalid credentialId handling (empty, non-string, invalid base64).
injected/unit-test/autofill-passkeys.js 94–101 info Mock messaging uses explicit field construction for notify payloads — no object spreading that could leak nativeData.

Risk Level

Low Risk — test-only additions plus import-path normalization in production code; no changes to API overrides, wrapper utilities, messaging transports, or security-sensitive initialization.

Recommendations

  1. No blocking issues. The test suite is thorough and exercises security-relevant edge cases (rpId type coercion, requestId correlation, abort handling, superseded requests).
  2. Optional follow-up (info): Consider a test for the randomUUID absent fallback path (line 77–79 in production), which passes through to originalGet when UUID generation is unavailable.
  3. Verified locally: All 16 specs in unit-test/autofill-passkeys.js pass.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

…ify pending

Bugbot finding (medium severity): the two specs that try to prove
a passkey request stays pending raced the request promise against
`Promise.resolve('pending')` — already settled. The race always
resolves to 'pending' regardless of whether the request promise
later settles, so the assertion provides no guard against
hang/early-completion regressions.

Replace the inline race with a settledOrPending() helper that races
against a setTimeout-based sentinel. It returns 'pending' only if
the promise hasn't settled within 50ms; otherwise it returns the
promise's resolved value or rejection reason. Both 'ignores ...
without a matching requestId' and 'ignores malformed credentialId
values' specs now actually verify the documented behaviour.

Co-authored-by: Jonathan Kingston <jonathanKingston@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale comment

Web Compatibility Assessment

No web-compatibility regressions identified in this diff.

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info Production change is limited to adding .js extensions on static imports (content-feature.js, captured-globals.js). This is a module-resolution convention with no runtime behavior change after bundling.
injected/unit-test/autofill-passkeys.js (all) info New unit tests exercise existing CredentialsContainer.prototype.get wrapping logic but do not ship to end users. Test harness globals (Object.defineProperty, setTimeout) are isolated to the Jasmine environment.

The tests document several existing pass-through guards that protect compatibility: non-conditional mediation, absent publicKey, non-secure contexts, and foreign CredentialsContainer receivers all bypass interception — matching native behavior expectations.

Security Assessment

No security vulnerabilities introduced by this diff.

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info No changes to API overrides, messaging payloads, captured-globals usage, or trust-boundary checks.
injected/unit-test/autofill-passkeys.js 222–242 info Tests confirm existing rpId hardening: non-string rpId values (including objects with a hostile toString) fall back to location.hostname via typeof check, not coercion. This documents correct existing behavior; no production change.
injected/unit-test/autofill-passkeys.js 327–341 info Tests confirm passkeySelected handler ignores payloads with mismatched requestId, documenting request-correlation protection already present in production code.

Outbound messaging in tests uses explicit { rpId, requestId } literals — no nativeData spread risk in production or test notify paths.

Risk Level

Low Risk — This PR adds unit-test coverage and normalizes import paths; it does not modify wrapper utilities, captured globals, messaging transports, or any injected runtime interception logic.

Recommendations

  1. No action required for web-compat or security on this diff.
  2. (Optional, info) Consider a Playwright integration test for the full navigator.credentials.get({ mediation: 'conditional', publicKey }) → native registerPasskeyRequestpasskeySelected round-trip if one does not already exist; unit tests here validate logic but not wrapMethod descriptor fidelity (toString masking, .name/.length preservation).
  3. (Optional, info) The settledOrPending helper is a good pattern for asserting promise pending state without microtask race false-positives — worth reusing in other async interception tests.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Web Compatibility & Security Review

Web Compatibility Assessment

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info Only change is adding .js extensions to import paths. No runtime behavior change; consistent with other feature files.
injected/unit-test/autofill-passkeys.js (all) info Test-only file. Does not ship to production bundles.
injected/unit-test/autofill-passkeys.js 399–495 info New tests validate existing pass-through guards: non-conditional mediation, missing publicKey, non-secure context (isSecureContext === false), and foreign CredentialsContainer receivers. These are important web-compat boundaries for the existing wrapMethod shim.
injected/unit-test/autofill-passkeys.js 478–494 info Tests confirm WebIDL-correct behavior: option getter throws surface as rejected promises, not synchronous throws — matching native credentials.get().

No web compatibility regressions introduced by this diff.

Security Assessment

File Lines Severity Finding
injected/src/features/autofill-passkeys.js 1–10 info Import-path-only change. No security surface change.
injected/unit-test/autofill-passkeys.js (all) info Test harness uses uncaptured Object.defineProperty / Reflect.deleteProperty on globalThis — acceptable in unit tests; not injected code.
injected/unit-test/autofill-passkeys.js 222–242 info Tests document an existing security property: rpId must be typeof === 'string'; objects with toString() are rejected in favor of location.hostname. Prevents coercion-based RP ID spoofing at the messaging layer.
injected/unit-test/autofill-passkeys.js 327–376 info Tests document inbound-message hardening already in production: requestId matching, empty/non-string credentialId ignored, invalid base64 rejects.
injected/unit-test/autofill-passkeys.js info Coverage gap (pre-existing feature, not a regression): no test for the randomUUID absence fallback (registerPasskeyRequest lines 77–80), which silently passes through to native get().

No security vulnerabilities introduced by this diff. Tests strengthen confidence in existing trust-boundary checks.

Risk Level

Low Risk — This PR adds unit tests and normalizes two import paths; it does not modify API overrides, messaging transports, captured globals, or initialization logic.

Recommendations

  1. (info) Consider adding a unit test for the randomUUID unavailable fallback path to lock in pass-through behavior.
  2. (info) Existing feature already uses wrapMethod (toString masking via wrapper-utils); an integration test asserting navigator.credentials.get.toString() returns [native code] would be a nice follow-up but is out of scope here.
  3. No blocking issues for merge from a web-compat or security perspective.

Automated review — Injected PR Evaluation (Web Compatibility & Security)

Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

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

Labels

semver-patch Bug fix / internal — no release needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants