Skip to content

feat: report the browser PAC per device, with an optional compliance check - #162

Open
LutzLange wants to merge 6 commits into
agentdesktop-dev:mainfrom
LutzLange:browser-pac-reporting
Open

LutzLange wants to merge 6 commits into
agentdesktop-dev:mainfrom
LutzLange:browser-pac-reporting

Conversation

@LutzLange

@LutzLange LutzLange commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

agentdesktop points AI tools at the LLM gateway, but the same models are one browser tab away. Enterprises steer browser AI traffic with a PAC file pushed by MDM or GPO, and nobody sees per device whether it landed. This adds that report. Related: #154 (security posture in the desktop view).

What it does

  • Report: per device, PAC absent, PAC found <url>, or differs (browsers disagree), read from Chrome, Edge, Chromium and Firefox machine policy: Linux policy files, Windows registry (HKCU only when the daemon runs as the user). Shown in agentdesktop discover, the desktop app and the fleet UI. Nothing is configured.
  • Identity: the daemon fetches each PAC URL and reports its SHA-256 and size (never the content). The fleet overview groups devices by URL and hash, so devices getting a different PAC, or none, stand out.
  • Compliance (optional): an expected URL in controller.yaml:
    browserEgress:
      compliance:
        pacUrl: https://pac.corp.example/ai-egress.pac
    The controller marks devices compliant or not, with the reason (absent, other URL, user-overridable policy). It lives in the controller config, not the pushed daemon config, so older daemons are unaffected.

Transport: Inventory.browser_pacs = 3 and Inventory.pac_fetches = 4; older controllers ignore them. No new dependencies (Windows adds the Win32_System_Registry feature of windows-sys).

Tested

  • Linux (Chrome 155, Firefox 157) and Windows 11 (Chrome, Edge, Firefox via HKLM): PAC and hash reported; Chrome applies the policy on a non-domain-joined Windows machine.
  • Fleet: a Linux daemon and the Windows service at one controller; a device on another PAC URL shows as a second PAC version and not compliant.
  • Unit tests for the readers (Windows logic behind a registry trait, so it runs everywhere), the fetch and cache, the compliance evaluation and storage.

Notes for review

Possible follow-up

This is meant as a first PR. A second one could add the other half for fleets without MDM (Linux, Windows) and for standalone use: agentdesktop writing the browser PAC policy itself, with the existing managed-file ownership and removal, verified by this report. Happy to hear whether that would fit before starting on it.

🤖 Generated with Claude Code

LutzLange and others added 4 commits October 20, 2026 13:05
…check

Browsers have no per-tool LLM setting; enterprises steer browser AI traffic
with a PAC file pushed by MDM or GPO. The daemon now reports, per device,
the PAC that browser machine policy sets: absent, found with its URL, or
differs between browsers.

- agent: Chrome, Edge, Chromium and Firefox policy readers for Linux
  (policy files) and Windows (registry; HKCU only when running as the
  user); the PAC URL is fetched directly and identified by SHA-256 and
  size, never its content; policy directories are watched
- proto: Inventory.browser_pacs = 3, Inventory.pac_fetches = 4
- controller: migration 0004_browser_pacs; optional
  browserEgress.compliance.pacUrl in the controller configuration, evaluated
  at read time (compliant or not, with the reason); PAC versions grouped by
  URL and hash on the overview
- UI: fleet device column, device card and overview tiles; desktop app card
- README section, regenerated schemas

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Linux: merge the proxy keys across policy files the way Chrome merges
  policies, so ProxyMode and ProxyPacUrl in different files still give a
  PAC; the same key with different values is still a conflict
- Linux: a policy directory that can't be read is reported instead of
  looking like "no policy"
- inventory refresh: resync the file watch after every scan, not only when
  the inventory changed, so a policy file in a directory created empty
  first is seen without waiting for the interval (regression test)
- fleet UI: when browsers set different PACs, the device page lists each
  PAC URL with its hash or fetch error

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Chromium policy files may contain comments and trailing commas, as
  Chromium's loader accepts them (JSON_PARSE_CHROMIUM_EXTENSIONS |
  JSON_ALLOW_TRAILING_COMMAS); the Firefox reader stays strict JSON
- a conflict between legacy ProxyMode/ProxyPacUrl values no longer hides
  a ProxySettings dictionary, which overrides them
- Linux Firefox: only the file Firefox uses counts
  (/etc/firefox/policies/policies.json when it exists, otherwise the
  install's distribution/ file), so a Proxy section in an ignored file
  can't be reported

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dev#160

agentdesktop-dev#160 added 0004_usage.sql; the browser PAC tables move to
0005_browser_pacs.sql. No content change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Policy-source selection can attribute inactive installation policies to installed browsers and produce incorrect compliance results.

2 open issues
What changed in this PR

Adds read-only browser PAC reporting to device inventory, extending desktop security visibility with PAC identity tracking and optional fleet compliance checks.

Changes:

  • Reads Linux and Windows browser policies and fetches PAC hashes and sizes.
  • Stores PAC inventory and evaluates compliance against a controller-configured URL.
  • Displays PAC status in the CLI, desktop app, and fleet UI, with documentation and tests.
File Description
schema/​controller-config.md Documents optional PAC compliance settings.
schema/​controller-config.json Adds the browser compliance configuration schema.
README.md Explains reporting, platform support, and compliance.
frontend/​ui/​src/​tools.tsx Adds friendly browser names.
frontend/​desktop/​src/​views/​ToolsView.tsx Displays browser PAC status and identity.
frontend/​desktop/​src/​types.ts Adds desktop PAC inventory types.
frontend/​desktop/​src/​stories/​fixtures.ts Adds sample browser policies and hashes.
frontend/​controller/​src/​views/​OverviewView.tsx Shows fleet PAC counts and version groups.
frontend/​controller/​src/​views/​DeviceView.tsx Shows device policies, identities, and compliance.
frontend/​controller/​src/​views/​DeviceView.stories.tsx Covers differing browser PACs.
frontend/​controller/​src/​types.ts Defines PAC report and compliance types.
frontend/​controller/​src/​styles.css Styles PAC status indicators.
frontend/​controller/​src/​stories/​fixtures.ts Adds fleet PAC reporting scenarios.
frontend/​controller/​src/​components/​DeviceTable.tsx Adds the Browser PAC column.
frontend/​controller/​src/​components/​BrowserPac.tsx Adds PAC status and compliance indicators.
crates/​proto/​proto/​fleet.proto Extends inventory with policies and fetch results.
crates/​core/​src/​model.rs Defines PAC models and device-level aggregation.
crates/​core/​src/​config.rs Parses and validates optional compliance settings.
crates/​controller/​src/​main.rs Passes the expected PAC URL to admin state.
crates/​controller/​src/​lib.rs Exposes the PAC evaluation module.
crates/​controller/​src/​database.rs Validates, stores, and loads PAC inventory.
crates/​controller/​src/​browser_pac.rs Evaluates compliance and groups PAC identities.
crates/​controller/​src/​admin.rs Adds PAC summaries to admin responses.
crates/​controller/​migrations/​0005_browser_pacs.sql Creates policy and fetch-result tables.
crates/​agent/​src/​remote.rs Sends PAC inventory to the controller.
crates/​agent/​src/​reconcile/​mod.rs Integrates policy discovery and PAC fetching.
crates/​agent/​src/​provider/​vscode/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​opencode/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​ollama/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​grok/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​cursor/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​copilot/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​codex/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​claude_desktop/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​provider/​claude_code/​mod.rs Initializes new inventory fields.
crates/​agent/​src/​llm_proxy.rs Updates test inventory fixtures.
crates/​agent/​src/​lib.rs Registers the PAC discovery module.
crates/​agent/​src/​inventory_watch.rs Watches browser policy directories.
crates/​agent/​src/​daemon.rs Logs PAC discovery and refreshes directory watches.
crates/​agent/​src/​cli.rs Prints PAC status and fetch results.
crates/​agent/​src/​browser_pac.rs Implements policy readers, bounded fetching, caching, and tests.
crates/​agent/​Cargo.toml Enables Windows registry APIs.

Review effort: Balanced

Comment thread crates/agent/src/browser_pac.rs Outdated
Comment on lines +450 to +454
let directories: Vec<PathBuf> = browser
.policy_roots
.iter()
.map(|policies| root.join(policies).join(level))
.collect();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d5e19af. Each Chromium installation now reads only the policy it sees, one row per installation:

  • snap: /etc/chromium-browser/policies only (the root its system-files plug exposes); a stale /etc/chromium/policies no longer counts
  • /usr/bin/chromium: /etc/chromium/policies
  • /usr/bin/chromium-browser: both roots, since the path varies by distribution; skipped when the snap is present, because on Ubuntu it only launches the snap
  • Flatpak: only its org.chromium.Chromium.Extension.* extensions (installed under /var/lib/flatpak/runtime/…/active/files/policies, unmanaged under /var/lib/flatpak/extension/…/policies), never host /etc
  • policy with no installation found: one row from all roots

each_installation_reads_only_its_own_policy covers a snap with stale system policy, the wrapper and a Flatpak extension. browser_pacs is now keyed by (device_id, browser, packaging).

Comment thread crates/agent/src/browser_pac.rs Outdated
Comment on lines +571 to +574
if let Some(file) = FIREFOX_POLICY_FILES
.iter()
.map(|file| root.join(file))
.find(|file| file.is_file())

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d5e19af. Firefox rows are now per installation:

  • Linux: /etc/firefox/policies/policies.json when it exists (it overrides every installation), otherwise that installation's own distribution/policies.json (/usr/lib, /usr/lib64, /opt/firefox). The snap reads only /etc/firefox/policies. The Flatpak is reported with "policy of the Flatpak build is not read", so a device with one doesn't show as having a PAC.
  • Windows: registry policy first, then the installation's own distribution\policies.json. The Program Files (x86) installation is its own row (x86), and a leftover file under %ProgramFiles% no longer counts for it.
  • Policy with no installation found: one row, from all locations.

Tests: each_installation_reads_only_its_own_policy (/opt/firefox with a leftover /usr/lib/firefox file, the global override, Flatpak) and firefox_rows_per_installation_with_registry_precedence (x86-only install with a stale x64 file, registry over file).

LutzLange and others added 2 commits October 23, 2026 09:00
One row per installation, from the policy that installation reads:
snap Chromium /etc/chromium-browser only, Flatpak Chromium its
org.chromium.Chromium.Extension.* extensions only, Firefox
/etc/firefox/policies or the installation's own distribution file,
Flatpak Firefox reported as not read. A browser with policy but no
installation found is one row from all its locations. Windows Firefox
reads the registry, then each installation's distribution file.
browser_pacs is keyed by (device_id, browser, packaging).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants