Skip to content

Latest commit

 

History

History
225 lines (179 loc) · 10.7 KB

AGENTS.md

File metadata and controls

225 lines (179 loc) · 10.7 KB

AGENTS.md

This file provides guidance to coding agents (Claude Code, Cursor, Codex, Aider, etc.) when working with code in this repository.

Branching workflow

This repo follows GitHub flow. For any new task, create a feature branch off main, push it, and open a PR — do not commit directly to main.

Repository Purpose

Prototyping browser extension capabilities for improving browser use agent performance:

  • Token efficiency
  • Security
  • Compliance: e.g., exposure to PII
  • Accuracy

Ideas explored in this repository:

  • Masking/Redacting sensitive information on the webpage
  • Blocking/Modifying dark patterns on the webpage
  • Preprocessing webpage content to be more agent-friendly, e.g., hiding irrelevant content, hiding user-generated comments which could contain prompt-injection attacks, etc.

Architecture decisions

Load-bearing decisions are recorded as MADR 4.0 ADRs under decisions/. Read the index before making changes that touch a recorded decision — rule taxonomy, frame execution, DOM markers, the lib/ ↔ rules/ boundary, SPA re-scan, scrub-don't-detach, shadow-DOM coverage, rule defaults, telemetry posture, injection-pattern handling, lint stack, background-worker purity, CSS-first hide, and the release flow.

Reference

Technology

  • Chromium Extension: Manifest V3, TypeScript, bun (bundling) assume Chrome 148+ for modern JS features
  • Python: use uv for all package management

Linting

Two linters run on extension/: Biome owns formatting plus its recommended rule set; ESLint runs only rules Biome doesn't have. The split is mechanical — do not duplicate a rule between them.

  • Biome config: extension/biome.json.
  • ESLint config: extension/eslint.config.js (flat config) — pulls in @eslint/js, typescript-eslint, and eslint-plugin-unicorn, then disables unicorn rules that overlap with Biome or are too opinionated for this repo.
  • Custom rules: extension/eslint-rules/*.js. Each rule is one file, exported via extension/eslint-rules/index.js under the agent-browser-shield/<rule-name> namespace. Add a rule by dropping a file in eslint-rules/, exporting it from index.js, and enabling it in eslint.config.js.
  • bun run check runs Biome then ESLint; bun run check:fix runs both with --fix/--write.

demo-site/ has a smaller ESLint config (demo-site/eslint.config.js) that just enforces unicorn/prevent-abbreviations so the React surface stays grep-friendly with the same naming conventions as the extension. The replacement allowlist (props, ref, args, loop counters, etc.) mirrors the extension's. bun run check in demo-site/ runs Biome + ESLint.

Rule ID naming

Rule IDs follow <target>-<verb> and the verb names what the rule does to the DOM. Five canonical verbs:

  • annotate — adds agent-readable info; page content unchanged (cart-addon-annotate, roach-motel-annotate)
  • hide — visually conceals via display: none; element stays in DOM (ads-hide, cookie-banner-hide, chat-widget-hide, newsletter-modal-hide)
  • redact — replaces content with a click-to-reveal placeholder (pii-redact, secrets-redact, comments-redact, prompt-injection-redact)
  • sanitize — keeps the element and cleans attributes/text/state (json-ld-sanitize, attribute-injection-sanitize, confirmshame-sanitize, checkout-checkbox-sanitize)
  • strip — removes the agent-readable content from the DOM. Most strip rules blank the data carrier (attribute value, text node, comment data) rather than detach the element, so SPA frameworks (React 19 metadata, Vue Teleport, Astro view transitions) keep live references to the rendered node and reconcile cleanly on route change. svg-sprite-strip is the exception — its targets are inline sprite definitions the page framework does not own, so it detaches the element outright. (noscript-strip, html-comment-strip, hidden-text-strip, svg-sprite-strip, svg-text-strip, meta-injection-strip)

-helper is reserved for non-defensive agent affordances (search-url-helper). See CONTRIBUTING.md → Adding a new rule → Rule ID naming for the longer write-up and the hide-vs-redact decision rule.

Rule authoring: re-scan SPA mutations

Rule apply runs once at document_idle. Client-side route changes in SPAs (React Router, Vue Router, etc.) swap subtrees in and out without a new page load, so anything that only ran in apply will never see post-navigation content — and most of our targets (PII, secrets, scarcity badges, hidden text) are exactly the kind of late-mounted content SPAs are built on.

Default to building any rule that mutates the DOM with the createScanRule factory (extension/src/lib/scan-rule.ts). Pass it the rule's id / label / description, a scanAndX(root) function, and skipPlaceholderSubtrees: true when the rule inserts placeholders; the factory owns the watcher lifecycle — the shared createSubtreeWatcher, the apply that scans then starts it, and the teardown that stops it — so callers don't re-hand-roll that skeleton (and can't forget the teardown or the placeholder skip). scarcity-redact, json-ld-sanitize, and hidden-text-strip are representative call sites.

Reach for createSubtreeWatcher directly only when the lifecycle is more than scan-on-apply-and-rescan: a second watcher / head router (meta-injection-strip), a route / focus subscription (form-prefill-annotate), a deferred snapshot reconcile (countdown-timer-redact), an onSubtrees that re-scans document.body instead of the surfaced root (cross-origin-frame-redact, svg-sprite-strip), or a teardown that restores page state (confirmshame-sanitize). Text-node redaction rules (pii-redact, secrets-redact, encoded-payload-redact) have their own factory, defineInlineTextRedactRule. Skip a watcher entirely only when there is nothing to re-scan after initial load — e.g., a one-shot landmark injection — and call that out in a comment.

DOM marker attributes

Every data-abs-* attribute the engine or any rule writes onto the page is declared and exported from extension/src/lib/dom-markers.ts — the single canonical registry. Engine-level markers are named <PURPOSE>_ATTR; per-rule markers are <RULE>_<PURPOSE>_ATTR. Import the constant; do not inline the literal. An ESLint no-restricted-syntax rule (eslint.config.js) blocks raw "data-abs-…" string and template literals everywhere except the registry itself, so name collisions and convention drift surface at lint time.

Rule defaults

The initial enabled/disabled state for each rule lives in extension/src/rules/rule-metadata.ts, not on the rule modules themselves. This hand-edited file is the source of truth for RuleId, RULE_IDS, and RULE_DEFAULTS; extension/src/lib/storage.ts imports it. It is kept out of rules/index.ts so the service-worker bundle doesn't pull rule files' top-level DOM access. Adding a rule means appending an entry both here and in rules/index.ts; the catalog test in extension/src/rules/__tests__/catalog.test.ts enforces that the two stay in sync.

extension/build.ts accepts a --defaults <path> CLI flag (or EXTENSION_DEFAULTS_FILE env var) pointing at a JSON file in the same shape as the Options-page export. Validated overrides are injected into the bundle via process.env.EXTENSION_DEFAULT_OVERRIDES. Override only affects fresh chrome.storage; existing user toggles persist.

Site-specific rule data

Selectors and URL recipes for individual sites live in extension/data/sites/*.yaml, not inline in the rule TS files. The codegen in extension/scripts/build-site-data.ts validates each YAML against the zod schema at extension/data/site-rules.schema.ts and emits extension/src/rules/site-data.generated.ts, which the rule files import.

The generated file is committed (matching the easylist-generic.generated.ts precedent). bun run build invokes codegen automatically; for a manual run use bun run build-site-data. To add or change site coverage, edit the YAML and rebuild — do not edit the generated file.

Prompt-injection patterns

Regex sources for prompt-injection-redact live base64-encoded in extension/data/injection-patterns.yaml. The codegen in extension/scripts/build-injection-patterns.ts decodes them and emits extension/src/rules/injection-patterns.generated.ts with plaintext RegExp literals — this keeps the shipped bundle free of atob-decoded strings (which Chrome Web Store review treats as obfuscated code) while still keeping literal adversarial phrasing out of files a coding agent is likely to read.

The generated file is committed and bun run build regenerates it; for a manual run use bun run build-injection-patterns. To add or change a pattern, edit the YAML and rebuild — do not edit the generated file.

Benchmark output management

Benchmark runs accumulate under output/results/<run_id>/ (raw events, traces, manifest) and output/reports/<run_id>*.html (diff viewers). Both directories are gitignored. To prune old artifacts:

uv run scripts/clean_artifacts.py            # dry-run, keeps 3 most recent
uv run scripts/clean_artifacts.py --keep 5 --apply
uv run scripts/clean_artifacts.py --orphans-only --apply  # only HTMLs whose run is gone

Dry-run by default; pass --apply to actually delete. See the script's --help for the full flag list.

Skills

When adding, removing, or renaming a defense rule (anything in extension/src/rules/ registered in extension/src/rules/index.ts), update skills/agent-browser-shield-config/SKILL.md so its rule ID list stays in sync. If the change also affects DOM markers or required agent behavior, update skills/agent-browser-shield/SKILL.md as well.

When changing the trace bundle layout, file names, or step schema produced by scripts/build_traces.py, update skills/agent-browser-shield-diagnose/SKILL.md so the diagnostic workflow it describes still matches what's on disk.

When changing the site-rule schema (SITE_DATA_RULE_IDS in extension/data/site-rules.schema.ts) or the Playwright MCP setup (.mcp.json), update skills/agent-browser-shield-site-rules/SKILL.md so its rule-type list and workflow stay in sync.

When changing build-time inputs in extension/build.ts (CLI flags, env vars, define: substitutions) — especially anything that affects how operators configure a deployed build — update skills/agent-browser-shield-install/SKILL.md and docs/src/content/docs/install.md so the build-time customization workflow stays accurate.