Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/browser-runtime-boundary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Browser Runtime Boundary

on:
pull_request:
paths:
- "schemas/browser-runtime-boundary-decision.schema.json"
- "examples/browser-runtime-boundary.*.json"
- "scripts/verify-browser-runtime-boundary.py"
- "docs/browser-runtime-boundary.md"
- ".github/workflows/browser-runtime-boundary.yml"
push:
branches:
- main
paths:
- "schemas/browser-runtime-boundary-decision.schema.json"
- "examples/browser-runtime-boundary.*.json"
- "scripts/verify-browser-runtime-boundary.py"
- "docs/browser-runtime-boundary.md"
- ".github/workflows/browser-runtime-boundary.yml"
workflow_dispatch:

permissions:
contents: read

jobs:
validate-browser-runtime-boundary:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Validate Browser Runtime Boundary fixtures
run: python3 scripts/verify-browser-runtime-boundary.py
54 changes: 54 additions & 0 deletions docs/browser-runtime-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# BearBrowser Runtime Boundary Decision

## Purpose

`BrowserRuntimeBoundaryDecision` is a decision-only record for BearBrowser runtime, automation, credential, and workspace-bridge surfaces.

It exists to prevent BearBrowser from collapsing policy text, credential mediation, browser automation, authenticated-session state, downloads, workspace bridges, and provenance events into a single implicit allow.

## Boundary chain

```text
browser / operator / agent request = evidence input
Policy Fabric decision = policy admission
Agent Registry ref = agent identity / authority evidence
BrowserRuntimeBoundaryDecision = local decision-only boundary
BearBrowser automation / credential broker / workspace bridge = later runtime surface
Provenance event = redacted evidence record only
```

## Hard rules

A valid boundary decision must keep:

- `performedAction = false`
- `credentialExportAllowed = false`
- `inheritsHumanCredentials = false`
- `nonLoopbackControlAllowed = false`
- `nativeExecutionAllowed = false`
- `declaredWorkspaceScopeOnly = true`
- `secretValuesLogged = false`
- `sessionMaterialLogged = false`
- `paymentMaterialLogged = false`

Agent actors must carry an Agent Registry ref. Policy decisions must be explicit refs. Evidence must be refs, not raw secrets or session material.

## Validation

```bash
python3 scripts/verify-browser-runtime-boundary.py
```

The verifier validates the good agent automation fixture and rejects fixtures that attempt credential export or raw secret logging.

## Related surfaces

- `TRUST_SURFACE.yaml`
- `policy/credential-broker-contract.yaml`
- `scripts/policy-surface`
- `scripts/credential-surface`
- `docs/provenance-events.md`

## Non-goals

This tranche does not execute browser automation, grant credential access, submit forms, bridge downloads to workspaces, send native messages, open non-loopback control, or mutate profiles. It only adds the boundary record and validation path.
38 changes: 38 additions & 0 deletions examples/browser-runtime-boundary.agent-automation.valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"schemaVersion": "bearbrowser.runtime-boundary.v1",
"kind": "BrowserRuntimeBoundaryDecision",
"decisionId": "browser-runtime-boundary:agent-automation-001",
"surface": "agent-browser-runtime",
"actor": {
"type": "agent",
"id": "agent://bearbrowser/test-agent",
"agentRegistryRef": "agent-registry://bearbrowser/test-agent"
},
"requestedAction": "agent_navigation",
"policyDecisionId": "policy-decision://bearbrowser/agent-navigation/allow-001",
"credentialBoundary": {
"credentialAccessRequested": false,
"credentialExportAllowed": false,
"inheritsHumanCredentials": false,
"osMediatedOnly": true
},
"automationBoundary": {
"automationRequested": true,
"nonLoopbackControlAllowed": false,
"nativeExecutionAllowed": false
},
"workspaceBoundary": {
"downloadOrWorkspaceBridgeRequested": false,
"declaredWorkspaceScopeOnly": true
},
"redactionBoundary": {
"secretValuesLogged": false,
"sessionMaterialLogged": false,
"paymentMaterialLogged": false
},
"performedAction": false,
"evidenceRefs": [
"evidence://bearbrowser/runtime-boundary/agent-automation-001"
],
"notes": "Decision-only record: policy admitted agent navigation, but this record performs no browser action and grants no credential authority."
}
36 changes: 36 additions & 0 deletions examples/browser-runtime-boundary.credential-export.invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"schemaVersion": "bearbrowser.runtime-boundary.v1",
"kind": "BrowserRuntimeBoundaryDecision",
"decisionId": "browser-runtime-boundary:credential-export-invalid",
"surface": "credential-broker",
"actor": {
"type": "agent",
"id": "agent://bearbrowser/test-agent",
"agentRegistryRef": "agent-registry://bearbrowser/test-agent"
},
"requestedAction": "credential_export",
"policyDecisionId": "policy-decision://bearbrowser/credential-export/deny-001",
"credentialBoundary": {
"credentialAccessRequested": true,
"credentialExportAllowed": true,
"inheritsHumanCredentials": false,
"osMediatedOnly": true
},
"automationBoundary": {
"automationRequested": false,
"nonLoopbackControlAllowed": false,
"nativeExecutionAllowed": false
},
"workspaceBoundary": {
"downloadOrWorkspaceBridgeRequested": false,
"declaredWorkspaceScopeOnly": true
},
"redactionBoundary": {
"secretValuesLogged": false,
"sessionMaterialLogged": false,
"paymentMaterialLogged": false
},
"performedAction": false,
"evidenceRefs": ["evidence://bearbrowser/runtime-boundary/credential-export-invalid"],
"notes": "Invalid fixture: credential export must remain denied."
}
36 changes: 36 additions & 0 deletions examples/browser-runtime-boundary.raw-secret.invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"schemaVersion": "bearbrowser.runtime-boundary.v1",
"kind": "BrowserRuntimeBoundaryDecision",
"decisionId": "browser-runtime-boundary:raw-secret-invalid",
"surface": "credential-broker",
"actor": {
"type": "agent",
"id": "agent://bearbrowser/test-agent",
"agentRegistryRef": "agent-registry://bearbrowser/test-agent"
},
"requestedAction": "credential_request",
"policyDecisionId": "policy-decision://bearbrowser/credential-request/deny-001",
"credentialBoundary": {
"credentialAccessRequested": true,
"credentialExportAllowed": false,
"inheritsHumanCredentials": false,
"osMediatedOnly": true
},
"automationBoundary": {
"automationRequested": false,
"nonLoopbackControlAllowed": false,
"nativeExecutionAllowed": false
},
"workspaceBoundary": {
"downloadOrWorkspaceBridgeRequested": false,
"declaredWorkspaceScopeOnly": true
},
"redactionBoundary": {
"secretValuesLogged": true,
"sessionMaterialLogged": false,
"paymentMaterialLogged": false
},
"performedAction": false,
"evidenceRefs": ["evidence://bearbrowser/runtime-boundary/raw-secret-invalid"],
"notes": "Invalid fixture: runtime boundary records must not log raw secret values."
}
83 changes: 83 additions & 0 deletions schemas/browser-runtime-boundary-decision.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.sourceos.dev/bearbrowser/browser-runtime-boundary-decision.schema.json",
"title": "BearBrowser Runtime Boundary Decision",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"kind",
"decisionId",
"surface",
"actor",
"requestedAction",
"policyDecisionId",
"credentialBoundary",
"automationBoundary",
"workspaceBoundary",
"redactionBoundary",
"performedAction",
"evidenceRefs"
],
"properties": {
"schemaVersion": { "const": "bearbrowser.runtime-boundary.v1" },
"kind": { "const": "BrowserRuntimeBoundaryDecision" },
"decisionId": { "type": "string", "minLength": 1 },
"surface": { "type": "string", "enum": ["human-secure-browser", "agent-browser-runtime", "automation-surface", "credential-broker", "workspace-bridge"] },
"actor": {
"type": "object",
"additionalProperties": false,
"required": ["type", "id"],
"properties": {
"type": { "type": "string", "enum": ["human", "agent", "service"] },
"id": { "type": "string", "minLength": 1 },
"agentRegistryRef": { "type": ["string", "null"] }
}
},
"requestedAction": { "type": "string", "minLength": 1 },
"policyDecisionId": { "type": "string", "minLength": 1 },
"credentialBoundary": {
"type": "object",
"additionalProperties": false,
"required": ["credentialAccessRequested", "credentialExportAllowed", "inheritsHumanCredentials", "osMediatedOnly"],
"properties": {
"credentialAccessRequested": { "type": "boolean" },
"credentialExportAllowed": { "const": false },
"inheritsHumanCredentials": { "const": false },
"osMediatedOnly": { "type": "boolean" }
}
},
"automationBoundary": {
"type": "object",
"additionalProperties": false,
"required": ["automationRequested", "nonLoopbackControlAllowed", "nativeExecutionAllowed"],
"properties": {
"automationRequested": { "type": "boolean" },
"nonLoopbackControlAllowed": { "const": false },
"nativeExecutionAllowed": { "const": false }
}
},
"workspaceBoundary": {
"type": "object",
"additionalProperties": false,
"required": ["downloadOrWorkspaceBridgeRequested", "declaredWorkspaceScopeOnly"],
"properties": {
"downloadOrWorkspaceBridgeRequested": { "type": "boolean" },
"declaredWorkspaceScopeOnly": { "const": true }
}
},
"redactionBoundary": {
"type": "object",
"additionalProperties": false,
"required": ["secretValuesLogged", "sessionMaterialLogged", "paymentMaterialLogged"],
"properties": {
"secretValuesLogged": { "const": false },
"sessionMaterialLogged": { "const": false },
"paymentMaterialLogged": { "const": false }
}
},
"performedAction": { "const": false },
"evidenceRefs": { "type": "array", "minItems": 1, "items": { "type": "string" }, "uniqueItems": true },
"notes": { "type": "string" }
}
}
111 changes: 111 additions & 0 deletions scripts/verify-browser-runtime-boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Verify BearBrowser runtime boundary decision fixtures."""
from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
SCHEMA = ROOT / "schemas" / "browser-runtime-boundary-decision.schema.json"
VALID = ROOT / "examples" / "browser-runtime-boundary.agent-automation.valid.json"
INVALID_CREDENTIAL_EXPORT = ROOT / "examples" / "browser-runtime-boundary.credential-export.invalid.json"
INVALID_RAW_SECRET = ROOT / "examples" / "browser-runtime-boundary.raw-secret.invalid.json"

REQUIRED = {
"schemaVersion",
"kind",
"decisionId",
"surface",
"actor",
"requestedAction",
"policyDecisionId",
"credentialBoundary",
"automationBoundary",
"workspaceBoundary",
"redactionBoundary",
"performedAction",
"evidenceRefs",
}


class BoundaryError(Exception):
pass


def load_json(path: Path) -> dict[str, Any]:
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise BoundaryError(f"{path.relative_to(ROOT)}: expected object")
return payload


def require(condition: bool, message: str) -> None:
if not condition:
raise BoundaryError(message)


def validate_schema(schema: dict[str, Any]) -> None:
require(schema.get("$schema") == "https://json-schema.org/draft/2020-12/schema", "schema draft mismatch")
require(schema.get("additionalProperties") is False, "schema must be closed")


def validate_boundary(path: Path, record: dict[str, Any]) -> None:
missing = sorted(REQUIRED - set(record))
require(not missing, f"{path}: missing fields: {missing}")
require(record.get("schemaVersion") == "bearbrowser.runtime-boundary.v1", f"{path}: schemaVersion mismatch")
require(record.get("kind") == "BrowserRuntimeBoundaryDecision", f"{path}: kind mismatch")
require(record.get("performedAction") is False, f"{path}: boundary record must not perform browser action")
require(str(record.get("policyDecisionId", "")).startswith("policy-decision://"), f"{path}: policyDecisionId required")

actor = record.get("actor", {})
require(isinstance(actor, dict), f"{path}: actor must be object")
if actor.get("type") == "agent":
require(actor.get("agentRegistryRef"), f"{path}: agent actor requires Agent Registry ref")

credential = record.get("credentialBoundary", {})
require(credential.get("credentialExportAllowed") is False, f"{path}: credential export must remain denied")
require(credential.get("inheritsHumanCredentials") is False, f"{path}: agent runtime must not inherit human credentials")

automation = record.get("automationBoundary", {})
require(automation.get("nonLoopbackControlAllowed") is False, f"{path}: non-loopback control must remain denied")
require(automation.get("nativeExecutionAllowed") is False, f"{path}: native execution must remain denied")

workspace = record.get("workspaceBoundary", {})
require(workspace.get("declaredWorkspaceScopeOnly") is True, f"{path}: workspace bridge must stay declared-scope only")

redaction = record.get("redactionBoundary", {})
require(redaction.get("secretValuesLogged") is False, f"{path}: secret values must not be logged")
require(redaction.get("sessionMaterialLogged") is False, f"{path}: session material must not be logged")
require(redaction.get("paymentMaterialLogged") is False, f"{path}: payment material must not be logged")

evidence_refs = record.get("evidenceRefs", [])
require(isinstance(evidence_refs, list) and evidence_refs, f"{path}: evidenceRefs required")
for ref in evidence_refs:
require(isinstance(ref, str) and ref.startswith("evidence://"), f"{path}: evidence refs must be evidence://")


def expect_invalid(path: Path) -> None:
try:
validate_boundary(path.relative_to(ROOT), load_json(path))
except BoundaryError:
return
raise BoundaryError(f"invalid fixture unexpectedly validated: {path.relative_to(ROOT)}")


def main() -> int:
try:
validate_schema(load_json(SCHEMA))
validate_boundary(VALID.relative_to(ROOT), load_json(VALID))
expect_invalid(INVALID_CREDENTIAL_EXPORT)
expect_invalid(INVALID_RAW_SECRET)
except (OSError, json.JSONDecodeError, BoundaryError) as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
print("BearBrowser runtime boundary fixtures verified")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading