143 lines
15 KiB
Markdown
143 lines
15 KiB
Markdown
# 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:
|
|
|
|
```json
|
|
{"action":"read","args":{}}
|
|
```
|
|
|
|
Run the packaged helper while the managed browser is running:
|
|
|
|
```powershell
|
|
& 'C:/path/to/SafetyNet.exe' --agent-connection='C:/absolute/profile/agent-connection.json' --agent-action='C:/absolute/request.json'
|
|
```
|
|
|
|
Development equivalent from the repository:
|
|
|
|
```powershell
|
|
./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}]` |
|
|
| 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](docs/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.
|