Implement managed browser security, individual agents and observability
This commit is contained in:
1 parent
ff482ebe23
commit
ea448de2d4
172 files changed
+5404
-347
No files matched your search
+81
-4
@@ -24,19 +24,26 @@ 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 sends bounded JSON with Node HTTP. 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.
|
||||
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
|
||||
|
||||
Send `POST /v1/actions` to the generated `http://127.0.0.1:<port>` endpoint with `Authorization: Bearer <token>` and `Content-Type: application/json`. Browser Origin/Fetch Metadata requests are rejected. No CORS, CDP, arbitrary JavaScript, cookie export or arbitrary filesystem API is exposed.
|
||||
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 |
|
||||
@@ -58,8 +65,78 @@ Inventory works on the start page. Read and AI can target an explicitly identifi
|
||||
|
||||
## Trusted integration contract
|
||||
|
||||
`startAgentControl({authorize,actions,audit,persistCredential?,credentialFile?,onError?,timeoutMs?,maxRequestsPerMinute?})` returns `{address,token,close,revoke}` asynchronously. It binds an ephemeral port only on 127.0.0.1. Defaults: 10-second action deadline, 60 authenticated requests/minute, 32 KiB request-body limit. Configure it exclusively in trusted main-process code.
|
||||
`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})` backed by protected trusted storage; Windows integration uses DPAPI. Optional `credentialFile` writes a new absolute path but does not establish Windows ACLs. 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.
|
||||
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.
|
||||
Reference in new issue
Block a user