Files
safetynet/AGENT-API.md
T

191 lines
19 KiB
Markdown
Raw Normal View History

2026-10-13 10:33:31 +02:00
# 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.
2026-10-13 10:33:31 +02:00
## 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.
2026-10-13 10:33:31 +02:00
| 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 |
| vault:status | optional sessionId | vault:status | vault:status | bounded exists/locked/enabled/autofill flags |
| vault:list | optional sessionId | vault:list | vault:list | approved origins and one-use credential references |
| vault:fill | sessionId, credentialRef | vault:fill | vault:fill | `{completed}`; does not authorize submission |
| vault:prepare-submit | sessionId, credentialRef | vault:prepare-submit | vault:prepare-submit | `{completed,prepared}`; never submits the form |
2026-10-13 10:33:31 +02:00
| 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}` |
2026-10-13 10:33:31 +02:00
| 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}` |
2026-10-13 10:33:31 +02:00
| 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}` |
2026-10-13 10:33:31 +02:00
| 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.
2026-10-13 10:33:31 +02:00
`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. Individually signed grants, generation rotation and targeted revocation are implemented below; protected pairing still 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 are implemented in
[individual agent administration](docs/individual-agent-grants.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.
## Controlled credential preparation
All vault scopes require explicit central permission. Unlock the vault through the
trusted human UI, then obtain a fresh `vault:list` reference for the exact native
login tab. `vault:fill` fills only. The separate `vault:prepare-submit` consumes its
reference, fills the approved form and issues a single-use local allowance; its
reply contains only completion/preparation booleans. Neither action submits the
form, unlocks the vault or exports username/password values. A later explicit
website submission still needs normal interaction permission and network checks.
An administrator must additionally enable
`passwordManager.controlledSubmission` and approve exact canonical HTTPS action
URLs plus distinct username/password field names. The current allowance expires
after 15 seconds and covers one bounded native main-frame, same-origin POST using
`application/x-www-form-urlencoded` (64 KiB, at most 128 distinct fields).
Exact credential values, document/frame, actor/generation, foreground activation,
policy and vault state are checked again. Other fields, request destination and
headers retain mandatory inspection. A mismatched candidate consumes permission;
never retry automatically. JS fetch/XHR, JSON, multipart and arbitrary login
protocols are outside this allowance. A 302-style GET receives ordinary inspection;
307/308 credential-body replay is blocked.
The approved website receives credentials in its DOM, where its JavaScript can
read them. The lease is a narrow network authorization mechanism, not password
isolation, form attestation or a guarantee against phishing. Website authentication
success is not returned by preparation or inferred from HTTP completion.
Security audit separates prepared, authorized, denied, HTTP-completed and
transaction-failure events. Authorization does not prove transmission; completion
means the native HTTP transaction finished, including error responses. Ordinary
metadata excludes credential values, form IDs, hosts, action URLs and body hashes.
Protected debug spans name `credential.issue`, `credential.claim`,
`credential.headers`, `credential.authorize`, `credential.redirect`,
`credential.complete` and `credential.failure`, with correlated inspection/audit
phases. Diagnostics remain centrally disableable and are not durable security audit.
The isolated native fixture has exercised approved delivery, safe 302, blocked307,
residual/duplicate/action/header refusals, human handoff and central revocation.
The latest source increment's final native rerun, full suite and Windows package
verification are pending; historical green builds do not prove this source state.
See [controlled credential policy](docs/controlled-credential-policy.md).