16 KiB
Local browser agents
The administrator enables policy.agents.enabled, chooses action-name scopes, and optionally restricts allowedHosts. Empty hosts inherits browser destination policy. Each native tab has a stable UUID for its lifetime. Obtain these IDs with sessions; an omitted sessionId captures the foreground tab once and pins the operation to it. Closed IDs are invalid. Screenshot scope is reserved but no screenshot API is implemented.
Windows pairing and CLI
The browser creates agent-connection.json in its Windows user-data directory. It contains format: windows-dpapi-v1 and a DPAPI CurrentUser-protected connection capability. The trusted shell's Agent connection control provides its path; remote pages never receive the token. Pairing grants access to trusted same-user local processes. It does not authenticate separate named applications or resist a compromised Windows account. Protect the profile and installation using endpoint management.
Create a local JSON request file containing:
{"action":"read","args":{}}
Run the packaged helper while the managed browser is running:
& 'C:/path/to/SafetyNet.exe' --agent-connection='C:/absolute/profile/agent-connection.json' --agent-action='C:/absolute/request.json'
Development equivalent from the repository:
./node_modules/electron/dist/electron.exe scripts/agent-client.cjs --connection 'C:/absolute/profile/agent-connection.json' --request 'C:/absolute/request.json'
The helper uses a separate profile, decrypts the descriptor with Windows DPAPI, verifies literal loopback, and uses the authenticated post-quantum transport helper. It follows no redirects and sets a deadline. Results go to stdout; failures exit with code 1. Protect/delete request files: prompts and typed text in those files are plaintext. A fresh capability is generated at startup; central revocation disables the current capability. A descriptor surviving a crash can point to an inactive endpoint and must not be treated as an authenticated response source without a running trusted browser.
Actions
Use agent-transport-client.cjs or the trusted Electron helper. The generated numeric-loopback endpoint requires a fresh HMAC-SHA384 proof bound to the exact body, route, timestamp and nonce; the capability token never travels on the wire. A pinned Ed25519/ML-DSA-87 signed one-use challenge establishes ML-KEM-1024/AES-256-GCM protection for action and reply. Plaintext successful replies and plaintext action envelopes are rejected. Browser Origin/Fetch Metadata requests are rejected. No CORS, CDP, arbitrary JavaScript, cookie export or arbitrary filesystem API is exposed.
| Action | Arguments | Policy scope | Internal scope | Result |
|---|---|---|---|---|
| sessions | none | sessions | sessions:list | [{id,title,foreground}] |
| sessions:save | name, sessionIds, optional id and expectedRevision | sessions:save | sessions:save | owned collection ID, revision, tabCount |
| sessions:list | none | sessions:list | sessions:list | revision and DLP-inspected owned collections |
| sessions:restore | id OR planId | sessions:restore | sessions:restore | one-use plan, then restored native tab IDs |
| navigate | sessionId, url | navigate | browser:navigate | {completed} |
| back | sessionId | back | browser:navigate | {completed} |
| forward | sessionId | forward | browser:navigate | {completed} |
| reload | sessionId | reload | browser:navigate | {completed} |
| read | sessionId | read | browser:read | {text} |
| inspect | sessionId | inspect | browser:read | sanitized controls and non-sensitive values |
| scroll | sessionId, x, y | scroll | browser:interact | {completed} |
| click | sessionId, selector | click | browser:interact | {completed} |
| type | sessionId, selector, text | type | browser:interact | {completed} |
| select | sessionId, selector, value | select | browser:interact | {completed} |
| check | sessionId, selector, checked | check | browser:interact | {completed} |
| ai | sessionId, prompt | ai | ai:request | {text} |
| application:state | none | application:state | application:state | ready, policyVersion, activeTabId, tabCount |
| settings:get | none | settings:get | settings:get | visible, hiddenSections, lockedSections, appearance |
| settings:update | appearance | settings:update | settings:update | current managed settings |
| bookmarks:list | none | bookmarks:list | bookmarks:list | managed and personal bookmarks, revision and permissions |
| bookmarks:mutate | action, bookmark/id, expectedRevision, policyVersion | bookmarks:mutate | bookmarks:mutate | {completed} |
| tabs:create | optional group, optional HTTP(S) url | tabs:create | tabs:create | {completed,sessionId} |
| tabs:activate | sessionId | tabs:activate | tabs:activate | {completed,sessionId} |
| tabs:close | sessionId | tabs:close | tabs:close | {completed} |
| tabs:group | sessionId, group | tabs:group | tabs:group | {completed,sessionId} |
Application control scopes are disabled by default and must be explicitly published by an administrator. They provide fixed operations on the same privileged helpers used by the browser UI. settings:update accepts only appearance.mode (system, light, dark), appearance.accent (blue, teal, violet) and appearance.tabMode (separate, compact). Hidden or locked settings remain enforced. Agents cannot edit signed security policy, enrollment, credentials or management keys. Bookmark changes target personal bookmarks only and require the current signed policy version and personal revision; company bookmarks remain protected. Bookmark titles, URLs and folders pass local DLP before export. Policy changes during an operation suppress its result, even if an authorized central bookmark mutation already committed; refresh before retrying.
Navigation accepts HTTP(S) URLs without credentials. Selectors use a conservative CSS subset. Targets must be visible and eligible; password, file, hidden, token and authentication fields are denied. Typing emits input/change events without directly submitting a form. Normal outbound DLP still gates site requests.
Read extracts at most 10,000 visible characters, excluding form/editable fields, hidden content, scripts/styles and sensitive elements. It returns no attributes, cookies or browser storage. Local inspection runs before return. allowSensitiveRead can permit known deterministic corporate DLP classifications; credentials, malware, unknown blocks and incomplete inspection stay denied. AI uses the centrally configured inspected gateway, with bounded inspected output. Detection does not guarantee recognition of every visible secret.
Inventory works on the start page. Read and AI can target an explicitly identified background tab; click/type require the foreground tab. Destination and host restrictions apply to the target tab. Adapters check readiness, expiry/version, action scope, destination and captured native identity before/after awaited operations. Navigation, same-URL reload, closure and policy changes invalidate captured operations. Foreground captures additionally become invalid after switching away and back; explicitly captured background reads remain bound to their original tab while other tabs are selected. Intentional navigation returns a freshly captured context for the same tab and preserves the foreground activation generation. Fixed DOM scripts bind to the captured document URL before interaction. No policy bypass or upload exception is generated. Page actions already performed cannot be undone by subsequent revocation; network requests still pass browser enforcement.
Trusted integration contract
startAgentControl({authorize,actions,audit,persistCredential?,credentialFile?,onError?,timeoutMs?,maxRequestsPerMinute?}) returns {address,token,edPublic,pqPublic,protocol,close,revoke} asynchronously. It binds an ephemeral port only on 127.0.0.1. Defaults: 10-second action deadline, 60 authenticated requests/minute, 128 KiB encrypted wire-body limit (the CLI request file remains bounded to 32 KiB). Configure it exclusively in trusted main-process code.
authorize({action,args,scope,sessionId,signal}) returns {allowed:true,role:'agent'|'admin',scopes:[requiredInternalScope]} after current policy checks. Action adapters receive (args,{signal}). createAgentAdapters({getContext,getSessions,contextCurrent,navigate,ai,inspect,audit,appControls?}) expects getContext(sessionId) to return {ready,sessionId,foreground,interactive?,token,policy,policyVersion?,webContents,destinationAllowed}. interactive:false prevents click/type when chrome panels cover the website. Omitted IDs capture the current foreground tab. getSessions() returns trusted native inventory. contextCurrent(token) verifies native identity, navigation generation, activation generation and policy generation. navigate(url,capturedContext,{signal}) must enforce the captured context before loading, and return the refreshed context for the same native tab after loading. appControls supplies fixed callbacks keyed by application action names; callbacks receive (args,{signal,policyVersion}) and must enforce managed visibility/locks, native tab identity and central bookmark CAS. The destination predicate must return true for allowed URLs. Adapters use fixed DOM code with JSON-escaped data, and honor abort.
Prefer persistCredential({address,token,edPublic,pqPublic,protocol}) backed by protected trusted storage; Windows integration uses DPAPI. Plaintext credentialFile persistence is rejected; use the protected persistence callback. Never accept callback/path configuration from pages. Audit contains action/scope/session/outcome metadata, not raw content. Audit failure prevents API adapter execution. Optional trusted onError reports static allowlisted reasons only. revoke() permanently disables the instance; close() shuts it down. Node deadlines cannot forcibly stop adapters that ignore cancellation.
Debug spans separately measure challenge, decryption, authorization, audit, dispatch, projection and encryption. Separate named per-agent identities and individual delegated revocation remain open; current pairing trusts processes of the paired Windows user.
select and check are separate opt-in policy scopes and require a visible interactive foreground tab. Selection chooses one existing enabled option; sensitive/hidden options or groups are excluded. Checkbox control is idempotent, uses page interaction semantics and verifies the requested state after handlers. Duplicate selectors, unknown options, radio inputs and credential targets are denied. Inspect exposes up to 20 safe options per select and checkbox states; option values longer than 128 characters are omitted instead of truncated. All returned metadata passes the existing DLP gate.
History navigation is native and centrally opt-in per action. It checks the native entry URL before dispatch and the refreshed managed context after completion; same-URL reload requires a real navigation event. Blocked entries, redirects away from the captured target, policy/tab races, unavailable entries and cancellation fail closed. This does not expose a browsing-history database or arbitrary URLs, and provides no policy bypass.
Authenticated agent replies now carry their status inside the encrypted agent-response object; the bundled client derives success/failure from that field and refuses missing or invalid encrypted status. Outer HTTP status is not authoritative. A typed browser-management-busy response for a bookmark mutation occurs before the change begins. Only that authenticated encrypted 409 is eligible for up to three retries (200/400/600 ms), each with a fresh challenge and unchanged policy/CAS arguments. Conflicts, revocation, uncertain failures and plaintext error responses never trigger automatic retry.
Durable agent events now carry a strict agent object with fixed action, matching
scope, outcome and optional requested native session UUID. Arguments, selectors,
prompts, tokens and fabricated principal IDs are excluded. Outcomes distinguish
authorization, completion, denial and failure; an untyped adapter exception is
reported as failure rather than inventing a policy-denial status. Completion and
failure audit phases now have explicit recording spans and trace correlation.
ECS preserves these fields under safetynet.agent; CEF uses bounded standard
custom-string fields without overwriting trace or audit-integrity references.
Legacy events without this object remain readable; they are not retroactively
attributed. Individual credential identities and targeted revocation remain
planned in individual-agent-identities.md.
Individually managed local identities
The signed agents.identityMode selects legacy shared pairing or individual.
In individual mode, select a centrally published, enabled and unexpired grant in
Local agents. The shell returns a hashed descriptor filename protected with
Windows DPAPI; it does not expose the token. Use that file with the existing
trusted helper. There is no shared master credential in individual mode.
Grants contain public ID/name, action scopes, additional allowed hosts, expiry and credential generation. Effective authority intersects global policy, assigned profile/group, grant rights and the adapter's own restrictions. The endpoint is not an admin policy editing API. Empty grant scopes grant no actions. Published revocation aborts owned work; unmodified peer credentials remain usable. A newly published policy is needed for offline devices to receive revocation.
A device retains the highest observed generation per ID in its encrypted enrollment. Restoring lower-generation grant metadata cannot revive it after a restart. Re-pair after generation rotation or browser restart: descriptors pin an ephemeral service identity and cannot authenticate the new listener. Credential possession identifies a capability; DPAPI CurrentUser does not prove which executable is using it.
Agent-initiated page actions bind subsequent tab network requests and popups to the initiating grant. Redirects and resource hosts must satisfy its allowlist. Explicit human address navigation releases this additional tab restriction while normal browser/DLP policy still applies. Library organization checks all involved metadata destinations before processing; it refuses mixed unauthorized scopes.
The broker's individual integration APIs are reconcilePrincipals(grants) and
issuePrincipal(id,generation,expiresAt). Issuance requires a matching approved
grant; main persists the returned descriptor atomically. Individual HMAC proofs
bind route, ID, generation, time, nonce and wire digest. Signed one-use challenges
also bind the service instance. Verified identity reaches adapters and durable
agent events as principalId/credentialGeneration; failed authentication is
unattributed. ECS uses safetynet.agent.principal_id; CEF suid represents the
agent capability and flexNumber1 its generation, not the employee identity.
Each request has an agent.request parent trace with timed child phases. No
payload arguments or credential secrets enter ordinary diagnostics. Revocation
cannot undo an already committed side effect; it prevents later work and
suppresses protected result release, including during asynchronous encryption.
Individual mode currently refuses browser-session HTTP requests without a known, guarded native tab owner, including unattributed service-worker/background traffic. This containment remains after human navigation clears an agent tab restriction. Supporting such traffic requires reliable actor ownership or isolated sessions; that compatibility work is open and is not satisfied by inferring the active tab.