Skip to content

fix(browser): grant ConnectBrowserLiveViewStream on *, as AWS requires - #1194

Merged
philmerrell merged 1 commit into
developfrom
fix/browser-live-view-connect-grant
Sep 19, 2026
Merged

philmerrell merged 1 commit into
developfrom
fix/browser-live-view-connect-grant

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Root cause

This is why the sign-in viewer never streamed. AWS's machine-readable service reference lists resource types per action:

action resource types
GetBrowserSession browser, browser-custom
UpdateBrowserStream browser, browser-custom
ConnectBrowserLiveViewStream [] — none

An action with no resource types never matches a resource-scoped statement. Granting all three on the browser ARN made the third an implicit deny.

iam simulate-principal-policy confirms it — from the same statement on the same ARN:

bedrock-agentcore:GetBrowserSession             allowed
bedrock-agentcore:UpdateBrowserStream           allowed
bedrock-agentcore:ConnectBrowserLiveViewStream  implicitDeny

Why it hid for so long

It fails silently and late. generate_live_view_url only signs locally — it calls no API — so app-api minted a URL happily. The denial appeared only when the browser opened the socket and the service closed it, surfacing as DCV auth code: 10, "Failed to communicate with server". That reads as a service fault, not a missing permission, and it sent me down three wrong paths.

How it was proven

By running the real DCV SDK against a real AgentCore session outside the browser (Node + jsdom + Node 22's native WebSocket):

  • With admin credentials: dcv.authenticate SUCCEEDS — returns sessionId + authToken, socket closes cleanly with code 1000.
  • From app-api: fails.

Same SDK, same service, same session shape — only the signing principal differs. That isolated it to IAM after subprotocol, Origin, take_control, doubled params, double-mount and CSP had each been ruled out by measurement.

Security

* is as narrow as this action can be expressed; there is no browser-scoped form. So the statement is split, carrying that one action and nothing else, while the resource-typed actions stay scoped to the browser ARN.

app-api still cannot start, stop or drive a browser. The existing "does NOT let app-api start, stop or drive" test now checks both statements, so the split can't become a side door.

Mutation-checked: re-scoping the connect grant fails the new assertion. Suite 876 pass, tsc --noEmit clean.

🤖 Generated with Claude Code

This is why the sign-in viewer never streamed. AWS's own service reference
lists the resource types for each action:

  GetBrowserSession            -> ['browser', 'browser-custom']
  UpdateBrowserStream          -> ['browser', 'browser-custom']
  ConnectBrowserLiveViewStream -> []          <- none

An action with NO resource types never matches a resource-scoped statement,
so granting all three on the browser ARN made the third an implicit deny.
`iam simulate-principal-policy` confirms it: allowed for the first two and
implicitDeny for the third, from the SAME statement on the SAME ARN.

It failed silently and late. `generate_live_view_url` only signs locally and
calls no API, so app-api minted a URL happily; the denial appeared only when
the browser opened the socket and the service closed it, surfacing as DCV
auth code 10, "Failed to communicate with server" — which reads as a service
fault, not a missing permission.

Proven by running the real SDK against a real session outside the browser:
`dcv.authenticate` SUCCEEDS with admin credentials (returns sessionId and
authToken, socket closes 1000) and fails from app-api. Same SDK, same
service, same session — only the signing principal differs.

`*` is as narrow as this action can be expressed. The statement is split so
it carries that one action and nothing else, and the resource-typed actions
stay scoped to the browser ARN. app-api still cannot start, stop or drive a
browser.

Mutation-checked: re-scoping the connect grant fails the new assertion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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