@modelprofile.com/browser-runtime 1.0.1 → 2.1.0

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/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @modelprofile.com/browser-runtime
2
2
 
3
- Parent-owned, project-scoped Chromium runtime with authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.
3
+ Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.
4
4
 
5
5
  ## Issue Reporting and Security
6
6
 
@@ -14,156 +14,127 @@ pnpm add @modelprofile.com/browser-runtime
14
14
 
15
15
  The runtime requires Node.js 24 or newer and a non-root Linux host. Production browser sessions require a sandbox-capable Chromium installation.
16
16
 
17
- ## Runtime
17
+ ## Resource Model
18
18
 
19
- `BrowserRuntime` owns its lock, profile root, artifact root, project slots, Chromium sessions, egress proxies, capabilities, leases, and cleanup work. The host must make the authorization decision; there is no permissive default.
19
+ The Controller owns durable resource and attachment truth. `BrowserRuntime` owns process-local registrations, fences, capabilities, leases, browser incarnations, artifacts, and its runtime lock. Registration never launches Chromium.
20
20
 
21
21
  ```typescript
22
- import { BrowserRuntime } from '@modelprofile.com/browser-runtime';
22
+ import {
23
+ BrowserRuntime,
24
+ type TBrowserCapabilityAuthorizationRequest,
25
+ } from '@modelprofile.com/browser-runtime';
23
26
 
24
27
  const runtime = new BrowserRuntime({
25
28
  runtimeDirectory: '/var/lib/example/browser-runtime',
26
- authorizeCapability: async ({ projectId, actorId, role, peerId, source }) => {
27
- return hostPolicy.authorizeBrowser({ projectId, actorId, role, peerId, source });
28
- },
29
- });
30
-
31
- await runtime.start();
32
-
33
- const issued = await runtime.issueCapability({
34
- projectId: 'project-123',
35
- actorId: 'agent-456',
36
- role: 'agent',
37
- peerId: 'worker-789',
38
- source: 'mcp',
39
- });
40
-
41
- // Deliver capabilityToken once to the exact authenticated peer.
42
- const lease = await runtime.acquireLease({
43
- capabilityToken: issued.capabilityToken,
44
- peerId: 'worker-789',
45
- expectedRole: 'agent',
46
- expectedSource: 'mcp',
29
+ authorizeCapability: async (binding) => hostPolicy.authorizeBrowser(binding),
30
+ beforeOperation: async (operation) => hostAudit.recordAttempt(operation),
47
31
  });
48
32
 
49
33
  try {
50
- const observation = await lease.executeAgentAction({ action: 'snapshot' });
51
- const screenshot = await lease.executeAgentAction({ action: 'screenshot' });
52
- console.log(observation, screenshot);
34
+ await runtime.start();
35
+
36
+ const resource = runtime.createResource({
37
+ projectId: 'project-123',
38
+ attachmentBinding: {
39
+ attachmentAuthorityId: 'controller-attachment-1',
40
+ attachmentRevision: 1,
41
+ sessionId: { harnessId: 'opencode', nativeId: 'session-456' },
42
+ },
43
+ });
44
+
45
+ const binding = {
46
+ projectId: resource.projectId,
47
+ browserResourceId: resource.browserResourceId,
48
+ attachmentAuthorityId: resource.attachmentBinding.attachmentAuthorityId,
49
+ attachmentRevision: resource.attachmentBinding.attachmentRevision,
50
+ sessionId: { harnessId: 'opencode', nativeId: 'session-456' },
51
+ actorId: 'agent-456',
52
+ role: 'agent',
53
+ peerId: 'worker-789',
54
+ source: 'mcp',
55
+ } satisfies TBrowserCapabilityAuthorizationRequest;
56
+
57
+ const issued = await runtime.issueCapability(binding);
58
+
59
+ const lease = await runtime.acquireLease({
60
+ ...binding,
61
+ capabilityToken: issued.capabilityToken,
62
+ });
63
+
64
+ try {
65
+ console.log(await lease.executeAgentAction({ action: 'snapshot' }));
66
+ } finally {
67
+ await lease.release();
68
+ }
53
69
  } finally {
54
- await lease.release();
55
70
  await runtime.stop();
56
71
  }
57
72
  ```
58
73
 
59
- Agent actions are limited to `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Screenshot results contain artifact metadata, never image bytes. Human leases additionally expose viewport, raw input, direct event/frame subscription, frame acknowledgement, and artifact read/delete methods. JavaScript evaluation is not part of either lease surface.
74
+ Projects and qualified sessions may each own many resources. Human/agent arbitration, mutexes, leases, operations, profiles, proxies, frame subscriptions, and idle timers are per resource.
60
75
 
61
- The first session for a project starts a private authenticated loopback proxy and runs the mandatory confinement probe before the session becomes available. The production probe verifies loopback denial, synthetic DNS traversal, and WebRTC candidate suppression. Browser/session and host-fact injection are not part of the published API.
76
+ `registerResource()` is idempotent only for the same project/resource key and identical attachment. `listResources()` reports process-local registration and incarnation metadata. `terminateResource()` terminates only the current incarnation and preserves registration, attachment, and artifacts. `retireResource()` permanently fences the process-local registration, revokes and quiesces authority, terminates its incarnation, purges exact-resource artifacts, and unregisters only after cleanup succeeds. The Controller separately owns durable retirement truth and must not rehydrate retired resources. Runtime tombstones and registrations are ephemeral, bounded process state. `stop()` revokes all process-local capabilities, terminates every incarnation, removes the Runtime-owned artifact root and registrations, and releases the lock without deleting Controller durable truth.
62
77
 
63
- ## Trusted Pipe
78
+ ## Attachment Fencing
64
79
 
65
- The parent attaches a trusted inherited pipe and supplies the peer, Flex scope, and Flex session identities out of band. Incoming frames cannot select a project, actor, role, source, scope, session, or peer. The capability used by the pipe must have been issued with the same immutable bindings.
80
+ Attachment bindings are Controller-owned `{ attachmentAuthorityId, attachmentRevision, sessionId }` values. Any binding with `sessionId: null` is detached; revision `0` is the initial detached/no-agent-authority state. Reapplying the identical revision and binding is idempotent; lower revisions and conflicting equal revisions fail.
66
81
 
67
82
  ```typescript
68
- import {
69
- BrowserRuntimeFramedClient,
70
- type BrowserRuntime,
71
- } from '@modelprofile.com/browser-runtime';
72
- import type { Readable, Writable } from 'node:stream';
73
-
74
- declare const runtime: BrowserRuntime;
75
- declare const childReadable: Readable;
76
- declare const childWritable: Writable;
77
-
78
- const flexCapability = await runtime.issueCapability({
79
- projectId: 'project-123',
80
- actorId: 'agent-456',
81
- role: 'agent',
82
- peerId: 'worker-789',
83
- source: 'flex',
84
- scopeId: 'project-123',
85
- sessionId: 'flex-session-1',
86
- });
87
-
88
- runtime.attachTrustedFramedPeer({
89
- peerId: 'worker-789',
90
- scopeId: 'project-123',
91
- sessionId: 'flex-session-1',
92
- readable: childReadable,
93
- writable: childWritable,
94
- });
95
-
96
- const childClient = new BrowserRuntimeFramedClient({
97
- scopeId: 'project-123',
98
- sessionId: 'flex-session-1',
99
- readable: process.stdin,
100
- writable: process.stdout,
101
- });
102
-
103
- await childClient.acquire(flexCapability.capabilityToken);
104
- const result = await childClient.executeAgentAction({
105
- action: 'navigate',
106
- url: 'https://example.com/',
83
+ await runtime.applyAttachmentBinding({
84
+ projectId: resource.projectId,
85
+ browserResourceId: resource.browserResourceId,
86
+ attachmentBinding: {
87
+ attachmentAuthorityId: 'controller-attachment-1',
88
+ attachmentRevision: 2,
89
+ sessionId: null,
90
+ },
107
91
  });
108
- console.log(result);
109
- await childClient.close();
110
92
  ```
111
93
 
112
- The framing protocol is versioned length-prefixed JSON with a 256 KiB hard frame ceiling, at most 16 pending requests, and a maximum 60-second request timeout. It supports only acquire, execute, cancel, and release.
94
+ A newer binding synchronously fences admission, revokes older capabilities, and quiesces active work. It normally preserves the incarnation; work that ignores cancellation causes termination of only that resource's incarnation.
113
95
 
114
- ## Flex Provider
96
+ Agent capabilities require the exact current non-detached qualified session. Human capabilities deliberately carry no session ID: they bind the exact project, resource, attachment authority, and revision and may be issued while detached. Any attachment revision advance invalidates both human and agent capabilities.
115
97
 
116
- `BrowserRuntimeFlexToolProvider<TScope>` implements `IFlexToolProvider<TScope>` for one supervised Flex child run. Its resolver returns only the capability token; the framed server validates that token against the parent-bound peer, source, scope, and session. The provider exposes SmartAgent browser tools with only the six allowed agent actions and maps side-effect permissions to `browser.<action>` Flex permissions. Closing or aborting the run releases the lease and closes the inherited-pipe client.
98
+ Agent actions are exactly `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press`. Human leases additionally expose tab lifecycle, viewport, raw input, frame subscription/acknowledgement, and exact-resource artifact reads/deletes. JavaScript evaluation is not public.
117
99
 
118
- ## MCP Handler
100
+ `beforeOperation` is an optional awaited fail-closed gate. It receives the complete immutable authority, operation/capability/lease IDs, action, start time, and an `AbortSignal` after resource admission but before the browser side effect starts. Hosts that require durable attempt-before-side-effect auditing should persist the attempt there. Rejection denies the operation. The default `beforeOperationTimeoutMs` is 10,000 milliseconds and accepts values from 100 through 120,000; timeout fails with `TIMEOUT`. The terminal `audit` callback remains a best-effort `completed`/`failed` notification correlated by the same operation ID and runs after bounded operation cleanup releases or fences the exact reservation.
119
101
 
120
- `createBrowserRuntimeMcpHttpHandler()` creates a stateless SmartMCP POST handler. Independent client proof is mandatory and the bearer capability must match the authenticated peer with role `agent` and source `mcp`.
102
+ ## Trusted Pipe And Flex
121
103
 
122
- ```typescript
123
- import { createBrowserRuntimeMcpHttpHandler } from '@modelprofile.com/browser-runtime';
104
+ Trusted framed peers and clients receive the complete authority out of band: project, resource, attachment authority/revision, actor, peer, role, source, qualified session, Flex scope, and resource-specific channel. Incoming frames cannot select identity. One session may use multiple resource-specific channels concurrently.
124
105
 
125
- const mcp = createBrowserRuntimeMcpHttpHandler(runtime, {
126
- authenticateMcpRequest: async (request) => {
127
- const peerId = await verifyClientCertificateOrSignedRequest(request);
128
- return { peerId };
129
- },
130
- allowedHosts: ['browser.example.com'],
131
- trustedOrigins: ['https://browser.example.com'],
132
- });
106
+ `BrowserRuntimeFlexToolProvider<TScope>` resolves only a capability token and exposes the six approved SmartAgent actions. The framed server validates the token against its complete trusted binding.
133
107
 
134
- export const fetch = (request: Request): Promise<Response> => mcp.handleRequest(request);
135
- ```
108
+ ## MCP Handler
136
109
 
137
- The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_click`, `browser_fill`, and `browser_press`. It does not expose state-only helpers, tab lifecycle, history, evaluation, frame streaming, artifact reads, raw input, or image bytes.
110
+ `createBrowserRuntimeMcpHttpHandler()` requires independent request authentication to return the complete expected MCP binding. The bearer capability must match it exactly. Identity remains server-owned and no tool input contains a project, resource, authority, revision, session, actor, or peer selector.
111
+
112
+ The MCP tool list is exactly `browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_click`, `browser_fill`, and `browser_press`.
138
113
 
139
114
  ## Egress And Artifacts
140
115
 
141
- `BrowserEgressProxy` is exported for testing and advanced composition. It uses Basic proxy authentication, one strict DNS/IP validation path for HTTP, WebSocket Upgrade, and CONNECT, public-global-only targets, numeric selected-IP dialing, default ports `80` and `443`, and bounded DNS, request, response, connection, and tunnel lifetimes. WebSocket tunneling starts only after a bounded valid `101` response.
116
+ Each running resource owns one authenticated loopback `BrowserEgressProxy` carrying immutable `projectId` and `browserResourceId`. HTTP, WebSocket Upgrade, and CONNECT share strict public-unicast DNS/IP validation, numeric dialing, bounded lifetimes, and fail-closed policy.
142
117
 
143
- `BrowserArtifactStore` exclusively creates and owns its root, writes generated artifact IDs below keyed project-digest directories, and removes the root on `stop()`. Directories use mode `0700`; files use mode `0600`. Serialized admission enforces per-file, per-project, project-count, aggregate-count, aggregate-byte, and TTL limits before writes. Reads use no-follow file handles and verify size plus SHA-256 integrity before returning trusted-host bytes. Agent, Flex, and MCP surfaces receive metadata only.
118
+ Artifact identity and APIs use `(projectId, browserResourceId, artifactId)`. Keyed project/resource directories prevent caller IDs from entering paths. Admission is serialized across per-resource, per-project, and global count/byte quotas. Reads use no-follow handles and verify size and SHA-256. Agent, Flex, and MCP surfaces receive metadata only.
144
119
 
145
120
  ## Verification
146
121
 
147
122
  ```sh
148
123
  pnpm install
124
+ pnpm dedupe
149
125
  pnpm run build
150
126
  pnpm run check:test
151
127
  pnpm test
152
- ```
153
-
154
- The real Chromium release check is intentionally separate:
155
-
156
- ```sh
157
128
  pnpm run test:real-chrome
158
129
  ```
159
130
 
160
- That command sets `BROWSER_RUNTIME_REAL_CHROME=1` and runs the production `LiveBrowserSession`, sandbox requirement, mandatory confinement probe, screenshot artifact path, confirmed shutdown, and profile deletion check.
131
+ The real Chromium check launches two resources in one project and qualified session, verifies distinct private profiles, sandboxed renderer process trees, mandatory confinement, screenshots, confirmed shutdown, and profile deletion.
161
132
 
162
133
  ## License and Legal Information
163
134
 
164
135
  This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
165
136
 
166
- **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
137
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the contents of the NOTICE file.
167
138
 
168
139
  ### Trademarks
169
140
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/browser-runtime',
6
- version: '1.0.1',
7
- description: 'Parent-owned, project-scoped Chromium runtime with authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.'
6
+ version: '2.1.0',
7
+ description: 'Parent-owned, resource-centric Chromium runtime with revisioned attachment fencing, authenticated human and agent control, fail-closed egress, bounded artifacts, and Flex/MCP adapters.'
8
8
  }
package/ts/actions.ts CHANGED
@@ -226,11 +226,20 @@ export const validateAgentActionResult = (value: unknown): TBrowserAgentActionRe
226
226
  validateExactKeys(value, ['action', 'artifact'], 'screenshot result');
227
227
  const artifact = validateExactKeys(
228
228
  result.artifact,
229
- ['artifactId', 'projectId', 'mimeType', 'size', 'createdAt', 'expiresAt'],
229
+ [
230
+ 'artifactId',
231
+ 'projectId',
232
+ 'browserResourceId',
233
+ 'mimeType',
234
+ 'size',
235
+ 'createdAt',
236
+ 'expiresAt',
237
+ ],
230
238
  'artifact metadata',
231
239
  );
232
240
  validateBoundedString(artifact.artifactId, 'artifactId', 1, 128);
233
- validateBoundedString(artifact.projectId, 'projectId', 1, 128);
241
+ validateBoundedString(artifact.projectId, 'projectId', 1, 256);
242
+ validateBoundedString(artifact.browserResourceId, 'browserResourceId', 16, 256);
234
243
  validateBoundedString(artifact.mimeType, 'mimeType', 1, 128);
235
244
  validateInteger(artifact.size, 'artifact.size', 0, 64 * 1024 * 1024);
236
245
  validateInteger(artifact.createdAt, 'artifact.createdAt', 0, Number.MAX_SAFE_INTEGER);