instar 1.3.1255 → 1.3.1256
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ChromeCdpReloginBrowser.d.ts +110 -2
- package/dist/core/ChromeCdpReloginBrowser.d.ts.map +1 -1
- package/dist/core/ChromeCdpReloginBrowser.js +524 -58
- package/dist/core/ChromeCdpReloginBrowser.js.map +1 -1
- package/dist/core/PasskeyBrowserPolicy.d.ts +61 -0
- package/dist/core/PasskeyBrowserPolicy.d.ts.map +1 -0
- package/dist/core/PasskeyBrowserPolicy.js +92 -0
- package/dist/core/PasskeyBrowserPolicy.js.map +1 -0
- package/dist/core/PasskeyWebAuthnFixture.d.ts +12 -0
- package/dist/core/PasskeyWebAuthnFixture.d.ts.map +1 -0
- package/dist/core/PasskeyWebAuthnFixture.js +113 -0
- package/dist/core/PasskeyWebAuthnFixture.js.map +1 -0
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/upgrades/1.3.1256.md +46 -0
- package/upgrades/side-effects/passkey-inc3-browser.md +216 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Side-Effects Review — Passkey-capable repair browser (Increment 3)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `passkey-inc3-browser`
|
|
4
|
+
**Date:** `2026-09-22`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `independent subagent (browser drives credential-bearing sign-ins)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Increment 3 of the approved spec `docs/specs/agent-held-google-passkey.md` (§3.5 browser foundation,
|
|
11
|
+
§3.1 in-browser credential lifetime, §2 fixture):
|
|
12
|
+
|
|
13
|
+
- **`src/core/PasskeyBrowserPolicy.ts` (new).** Pure rules for WHERE the passkey may be present:
|
|
14
|
+
the credential is added only while the top-level frame is on the holder origin and no other frame
|
|
15
|
+
is on a different relying-party-family origin; it is removed at request time before any top-level
|
|
16
|
+
navigation away from the holder origin, and before any frame's document request to another
|
|
17
|
+
RP-family origin; unrelated-origin frames are allowed. Parameterised by an `OriginPolicy` so the
|
|
18
|
+
same code runs against the local fixture; production uses the Google policy.
|
|
19
|
+
- **`src/core/ChromeCdpReloginBrowser.ts` (extended).** A `passkeyMode` option: CDP over the
|
|
20
|
+
debugging pipe (no TCP port, no `DevToolsActivePort`), `--disable-extensions`,
|
|
21
|
+
`--disable-component-extensions-with-background-pages`, prerendering off; every page/iframe target
|
|
22
|
+
auto-attached (paused at creation) and PREPARED — Page/Runtime/Network enabled, service workers
|
|
23
|
+
bypassed, `Fetch` interception for Document requests, `WebAuthn.enable` + a virtual authenticator,
|
|
24
|
+
the loaded credential added — before it is resumed; a `Fetch.requestPaused` handler that removes the
|
|
25
|
+
credential everywhere before continuing a document request the policy forbids, and fails the
|
|
26
|
+
request closed if removal fails. New port methods: `addCredential`, `removeCredential`,
|
|
27
|
+
`hasCredential`, `observedAssertion` (from `WebAuthn.credentialAsserted`), `exportCredentials`,
|
|
28
|
+
`credentialCount(s)`, `readSignedInIdentityMatches` (boolean only), `sawNavigationBeforeAttach`,
|
|
29
|
+
`clickSelector`, `runInPage`. Clicks now use REAL pointer events (`Input.dispatchMouseEvent`) in
|
|
30
|
+
BOTH modes, including the existing `chooseExpectedAccount`, `click` and the submit step of
|
|
31
|
+
`fillAndSubmit`; fills read the value back before submitting. The legacy TCP path is otherwise
|
|
32
|
+
unchanged and remains the default (`passkeyMode` is off unless a caller sets it).
|
|
33
|
+
- **`src/core/PasskeyWebAuthnFixture.ts` (new).** Two local HTTP origins on `localhost` that stand in
|
|
34
|
+
for accounts.google.com and another google.com subdomain, with pages that create and assert
|
|
35
|
+
resident credentials. Used by the integration suite now and by the Chrome-version self-check later.
|
|
36
|
+
|
|
37
|
+
No caller constructs the browser in passkey mode yet (`server.ts` still uses the default), so there is
|
|
38
|
+
no production behaviour change from passkey mode itself. The real-click change does reach the
|
|
39
|
+
existing repair path.
|
|
40
|
+
|
|
41
|
+
## Decision-point inventory
|
|
42
|
+
|
|
43
|
+
- `mustRemoveCredentialBefore` / `mayAddCredential` — add — invariants over origins (spec §10 rows "Credential-affecting browser actions" / all-frames rule).
|
|
44
|
+
- `Fetch.requestPaused` handler — add — applies the policy; fail-closed on removal error.
|
|
45
|
+
- Real-click substitution in existing repair actions — modify — no decision change; input mechanics only.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 1. Over-block
|
|
50
|
+
|
|
51
|
+
- In passkey mode, a document request whose URL cannot be parsed removes the credential (fail closed).
|
|
52
|
+
- Real clicks need a visible, non-zero-size element; a hidden button that `element.click()` used to
|
|
53
|
+
reach now raises `browser-element-not-found`. The existing integration test (real Chrome) still
|
|
54
|
+
passes, and Google's list items REQUIRE real clicks (measured 2026-09-21), so this is the right
|
|
55
|
+
trade.
|
|
56
|
+
- Fills now refuse to submit when the value did not stick (read-back length check). Before, an empty
|
|
57
|
+
submit cost a failed attempt on the account.
|
|
58
|
+
|
|
59
|
+
## 2. Under-block
|
|
60
|
+
|
|
61
|
+
- A same-origin (holder) frame can still request an assertion — by design; that is the sign-in page.
|
|
62
|
+
- Prerendering is disabled by feature flag; if Chrome renames the feature, prerender targets would be
|
|
63
|
+
auto-attached like any other target and prepared before running, so the interception still holds,
|
|
64
|
+
but the flag itself would be inert. The fixture suite would not notice a rename; noted for the
|
|
65
|
+
Chrome-version gate increment. <!-- tracked: CMT-544 -->
|
|
66
|
+
- `sawNavigationBeforeAttach` observes from `Page.enable` onward, so it can confirm a breach it saw
|
|
67
|
+
but not prove absence. The load-bearing guarantee is Chrome's `waitForDebuggerOnStart` pause
|
|
68
|
+
(a target cannot navigate until it is resumed), which the popup tests exercise directly.
|
|
69
|
+
- `readSignedInIdentityMatches` is a generic structural reader (attributes, then exact-text leaf
|
|
70
|
+
elements). The closed `google-account-identity` page class with its fixture lands with the
|
|
71
|
+
enrollment increment; until then it is used only by tests.
|
|
72
|
+
|
|
73
|
+
## 3. Level-of-abstraction fit
|
|
74
|
+
|
|
75
|
+
The policy is a pure module; the browser applies it at the one place every document request passes
|
|
76
|
+
(Fetch interception at the Request stage). The port grows only by the methods the spec names.
|
|
77
|
+
|
|
78
|
+
## 4. Signal vs authority compliance
|
|
79
|
+
|
|
80
|
+
Origin rules are deterministic invariants over URLs, not content detectors, and they can only remove
|
|
81
|
+
or refuse — never act on the account. The supervisor is untouched.
|
|
82
|
+
|
|
83
|
+
## 4b. Judgment-point check (Judgment Within Floors standard)
|
|
84
|
+
|
|
85
|
+
Invariants (spec §10). No competing signals.
|
|
86
|
+
|
|
87
|
+
## 5. Interactions
|
|
88
|
+
|
|
89
|
+
- The existing repair driver is unaffected except that its clicks are now real pointer events and
|
|
90
|
+
fills read back; the existing driver unit tests (15) and the real-Chrome integration test pass.
|
|
91
|
+
- In passkey mode, `Runtime.evaluate` and clicks target the MAIN page session; popups are prepared and
|
|
92
|
+
resumed but not driven (no spec step drives a popup).
|
|
93
|
+
- `close()` clears the in-memory credential first, then closes the browser.
|
|
94
|
+
|
|
95
|
+
## 6. External surfaces
|
|
96
|
+
|
|
97
|
+
None: no route, no notice, no config. Chrome launch flags change only in passkey mode.
|
|
98
|
+
|
|
99
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
100
|
+
|
|
101
|
+
No operator surface.
|
|
102
|
+
|
|
103
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
104
|
+
|
|
105
|
+
A browser session is machine-local by nature (it drives this machine's Chrome and profile). The
|
|
106
|
+
credential material it holds in memory comes from the machine-local store (Increment 2) and never
|
|
107
|
+
leaves the process. No state is written by this increment.
|
|
108
|
+
|
|
109
|
+
## 8. Rollback cost
|
|
110
|
+
|
|
111
|
+
Pure code. Reverting restores the TCP-only browser and script clicks. No persistent state.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Conclusion
|
|
116
|
+
|
|
117
|
+
The browser can now hold a passkey safely: attached before any navigation, removed before any
|
|
118
|
+
document request that could reach another relying-party origin, and driven with real input. Every
|
|
119
|
+
spec-named case is covered by a real-Chrome test against the fixture, and the three load-bearing
|
|
120
|
+
behaviours were mutation-checked (removal, add-time origin check, popup loading). Clear to ship after
|
|
121
|
+
the second-pass review.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Second-pass review (if required)
|
|
126
|
+
|
|
127
|
+
**Reviewer:** independent subagent (Claude), 2026-09-22 20:33 PDT — **Concern raised**, then fixed
|
|
128
|
+
and re-verified (round 2 below).
|
|
129
|
+
|
|
130
|
+
Round 1 findings (verbatim substance):
|
|
131
|
+
|
|
132
|
+
1. **`addCredential` ignored other page targets' origins.** The origin walk covered only the main
|
|
133
|
+
target's tree plus iframe targets; popups (`type:'page'`) were never inspected, yet the add then
|
|
134
|
+
loaded the credential into EVERY prepared authenticator. A popup that had earlier moved to
|
|
135
|
+
`myaccount.google.com` (removal fired correctly then) would be re-armed by a later add on the
|
|
136
|
+
holder-origin main page. Spec §3.1 is per-target.
|
|
137
|
+
2. **Removal was gated on an in-memory flag, and prepare/remove could interleave.** The sweep skipped
|
|
138
|
+
sessions without an `authenticatorId`; a popup mid-`prepareSession` could be skipped, then read
|
|
139
|
+
`this.credential` before the null write and load it. That target then held the key while
|
|
140
|
+
`hasCredential()` was false, and the request-time hook's flag check short-circuited — its own
|
|
141
|
+
forbidden first navigation would be continued WITHOUT removal.
|
|
142
|
+
3. Minor: `topLevel` fell OPEN when `Page.getFrameTree` had failed (a top-level move to a non-RP
|
|
143
|
+
origin kept the key); `sawNavigationBeforeAttach` is a weak diagnostic, not the spec's proof;
|
|
144
|
+
`runInPage`/`clickSelector` were public arbitrary-evaluate surfaces restricted only by a comment.
|
|
145
|
+
|
|
146
|
+
Confirmed OK by the reviewer: `continueRequest` strictly after `removeCredential` resolves,
|
|
147
|
+
timeout/non-"gone" errors → `failRequest`; pipe framing/close handling; real clicks in both modes
|
|
148
|
+
with the `form.requestSubmit()` fallback and read-back gate; the mutation claims are credible against
|
|
149
|
+
the listed tests; no material is logged (`exportCredentials` is the only egress).
|
|
150
|
+
|
|
151
|
+
**Fixes (all in `src/core/ChromeCdpReloginBrowser.ts`):**
|
|
152
|
+
|
|
153
|
+
- (1) `addCredential` now walks EVERY page target: the main target must satisfy `mayAddCredential`;
|
|
154
|
+
every secondary target (popup) must have no frame on another RP-family origin
|
|
155
|
+
(`mayAddSecondaryTarget` — blank/paused, holder-origin, or outside the RP family all pass). A popup
|
|
156
|
+
on another RP-family origin vetoes the add with `passkey-origin-not-allowed`.
|
|
157
|
+
- (2) One credential lock (`withCredentialLock`) serialises add, remove and the prepare-time load; in
|
|
158
|
+
`prepareSession` the authenticator becomes visible to removal AND receives the credential in the
|
|
159
|
+
same locked step, so a concurrent removal runs entirely before (credential null ⇒ nothing loaded)
|
|
160
|
+
or entirely after (the target is in its sweep). Removal no longer trusts the flag: it sweeps every
|
|
161
|
+
id this browser EVER loaded (`knownCredentialIds`) from every authenticator, and the request-time
|
|
162
|
+
hook runs whenever any id is known.
|
|
163
|
+
- (3) Unknown frame identity (no session record / no frame tree) is treated as TOP-LEVEL — the
|
|
164
|
+
stricter rule; over-remove, never under-remove. `runInPage` / `clickSelector` are refused under the
|
|
165
|
+
production Google policy (`*-fixture-only`), so the arbitrary-evaluate surface exists only against
|
|
166
|
+
the local fixture. `sawNavigationBeforeAttach` is documented as a diagnostic.
|
|
167
|
+
|
|
168
|
+
**Evidence added:** `tests/unit/chrome-cdp-passkey-credential-race.test.ts` (7 tests against a
|
|
169
|
+
scripted CDP `send`, no Chrome): the exact skip-mid-prepare interleave (a later session keeps the
|
|
170
|
+
sweep busy while the popup finishes preparing) → no authenticator holds the key; a popup attached
|
|
171
|
+
before the removal is swept; a popup on another RP-family origin vetoes the re-add and stops vetoing
|
|
172
|
+
once back on the holder origin; an unrelated-origin popup does not veto; unknown frame identity
|
|
173
|
+
removes on a non-RP top-level move; a known subframe on an unrelated origin keeps the key; the
|
|
174
|
+
fixture-only refusal under the Google policy. Mutation checks: with the lock removed the interleave
|
|
175
|
+
test fails (`[0,0,1]`); with the secondary-target veto removed the popup-veto test fails; with the
|
|
176
|
+
fail-open `topLevel` restored the unknown-identity test fails. Real-Chrome: a new fixture test opens
|
|
177
|
+
a popup, moves it (by window name) to the other RP-family origin, sees `[0,0]`, gets the re-add
|
|
178
|
+
refused, moves it back, and sees `[1,1]`.
|
|
179
|
+
|
|
180
|
+
**Round 2:** the reviewer's two gaps are closed by structure (per-target check; lock + by-id sweep),
|
|
181
|
+
each with a test that fails when its fix is removed. Concur.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Evidence pointers
|
|
186
|
+
|
|
187
|
+
- `tests/unit/passkey-browser-policy.test.ts` — 20 tests, production and fixture policies.
|
|
188
|
+
- `tests/unit/chrome-cdp-passkey-credential-race.test.ts` — 7 scripted-CDP tests for the second-pass
|
|
189
|
+
findings (interleave, popup veto, unknown frame identity, fixture-only surfaces).
|
|
190
|
+
- `tests/integration/passkey-browser-fixture.test.ts` — 11 real-Chrome tests: pipe transport, mint →
|
|
191
|
+
export → re-inject → assert (portability), add refused off the holder origin, removal on top-level
|
|
192
|
+
move / 302 redirect / RP-family iframe, unrelated iframe keeps it, popup auto-attach with both
|
|
193
|
+
authenticators loaded, popup-on-other-origin vetoes a re-add until it returns, identity read, real
|
|
194
|
+
clicks. Mutation checks: with request-time removal
|
|
195
|
+
disabled the three removal tests fail; with the add-time origin check disabled the refusal test
|
|
196
|
+
fails; with new-target loading disabled the popup test fails.
|
|
197
|
+
- `tests/integration/chrome-cdp-relogin-browser.test.ts` (existing, real Chrome, TCP mode) and
|
|
198
|
+
`tests/unit/anthropic-relogin-browser-driver.test.ts` pass unchanged.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Class-Closure Declaration (display-only mirror)
|
|
203
|
+
|
|
204
|
+
- `unbounded-self-action` — closure: **n/a** — reason: one-shot user-driven action, not a
|
|
205
|
+
self-triggered loop: `ChromeCdpReloginBrowser.open()` spawns exactly one Chrome process per
|
|
206
|
+
operator-approved repair episode (the orchestrator bounds attempts); the browser never restarts,
|
|
207
|
+
retries or respawns itself.
|
|
208
|
+
- No agent-authored-artifact defect — not applicable. New capability from an approved spec; no
|
|
209
|
+
self-triggered controller is added.
|
|
210
|
+
|
|
211
|
+
## Post-review follow-up (CI ratchet)
|
|
212
|
+
|
|
213
|
+
- `no-silent-fallbacks` counted the two pure-parse catches in `PasskeyBrowserPolicy` (`originOf` → null,
|
|
214
|
+
`isRpFamilyOrigin` → false). Neither hides a decision: null/false are the STRICT branches (removal on an
|
|
215
|
+
unparseable URL; "not in the family" on an unparseable origin). Annotated `@silent-fallback-ok` with that
|
|
216
|
+
reason rather than raising the baseline. No behaviour change.
|