Skip to content
vsromancPublic

About

Prototype Chrome extension (MV3) for high-performance network interception across tabs and frames using SharedWorker, hidden iframes, and provider-based DLP-style detection.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

2 Commits

Folders and files

Repository files navigation

Framelink

Prototype: an experimental Chrome extension / MV3 architecture for high-performance network interception across tabs and frames using content scripts, hidden iframes, MessageChannel, and SharedWorker primitives.

Keywords: chrome extension, mv3, browser extension, dlp, data loss prevention, network interception, shared worker, web worker, content script, iframe, cross-frame, cross-tab, browser security, SaaS monitoring, Google Drive, insider risk.

System schema

                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                         β”‚        Chrome Extension (MV3)        β”‚
                         β”‚                                      β”‚
                         β”‚  background.ts                       β”‚
                         β”‚  - receives domain events            β”‚
                         β”‚  - logs / persists / forwards        β”‚
                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚ chrome.runtime.sendMessage
                                         β”‚
                              domain events from worker
                                         β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚         bridge.html / bridge.ts         β”‚
                    β”‚      hidden iframe in each frame        β”‚
                    β”‚  - owns SharedWorker connection         β”‚
                    β”‚  - forwards page MessagePorts           β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚
                               MessagePort / postMessage
                                         β”‚
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚                               β”‚                               β”‚
         β”‚                               β”‚                               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Tab A Frame 1 β”‚             β”‚   Tab A Frame 2 β”‚             β”‚   Tab B Frame 1 β”‚
β”‚                 β”‚             β”‚                 β”‚             β”‚                 β”‚
β”‚ content.ts      β”‚             β”‚ content.ts      β”‚             β”‚ content.ts      β”‚
β”‚ - host filter   β”‚             β”‚ - host filter   β”‚             β”‚ - host filter   β”‚
β”‚ - injects page  β”‚             β”‚ - injects page  β”‚             β”‚ - injects page  β”‚
β”‚ - injects iframeβ”‚             β”‚ - injects iframeβ”‚             β”‚ - injects iframeβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                               β”‚                               β”‚
         β”‚ injects                       β”‚ injects                       β”‚ injects
         β”‚                               β”‚                               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ page.ts         β”‚             β”‚ page.ts         β”‚             β”‚ page.ts         β”‚
β”‚ - patches fetch β”‚             β”‚ - patches fetch β”‚             β”‚ - patches fetch β”‚
β”‚ - patches XHR   β”‚             β”‚ - patches XHR   β”‚             β”‚ - patches XHR   β”‚
β”‚ - emits request β”‚             β”‚ - emits request β”‚             β”‚ - emits request β”‚
β”‚   / response    β”‚             β”‚   / response    β”‚             β”‚   / response    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                               β”‚                               β”‚
         └─────────────── MessageChannel / PagePort β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚
                                         β–Ό
                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                          β”‚       shared-worker.ts       β”‚
                          β”‚  single processing hub       β”‚
                          β”‚                              β”‚
                          β”‚  NetworkPipeline             β”‚
                          β”‚  RequestTracker              β”‚
                          β”‚  Provider plugins            β”‚
                          β”‚   - google-drive             β”‚
                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚
                                         β”‚ normalized request/response
                                         β”‚
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚   packages/interception-core     β”‚
                        β”‚   - protocol.ts                  β”‚
                        β”‚   - request-tracker.ts           β”‚
                        β”‚   - pipeline.ts                  β”‚
                        β”‚   - types.ts                     β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚
                                         β–Ό
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚ packages/provider-google-drive   β”‚
                        β”‚ - matches Drive batch requests   β”‚
                        β”‚ - parses sharing payload         β”‚
                        β”‚ - emits domain event             β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Request lifecycle

1. User action on page
   -> page issues fetch() or XMLHttpRequest

2. page.ts
   -> patched fetch/XHR captures:
      - id
      - method
      - url
      - body
      - timestamps

3. content.ts
   -> has already connected page <-> hidden iframe
      with MessageChannel

4. bridge.ts
   -> forwards page port into SharedWorker

5. shared-worker.ts
   -> RequestTracker stores request
   -> later attaches response
   -> NetworkPipeline runs hooks

6. Provider hook matches transaction
   -> e.g. Google Drive sharing request

7. Provider emits domain event
   -> e.g. google-drive.sharing-detected

8. bridge.ts
   -> forwards domain event to background.ts

9. background.ts
   -> logs / stores / forwards event to product backend

Mermaid

flowchart TD
  A[Page frame] --> B[content.ts]
  B --> C[Inject page.ts]
  B --> D[Inject hidden bridge iframe]

  C --> E[Patch fetch and XHR]
  E --> F[Emit normalized request/response]

  F --> G[MessageChannel]
  G --> D

  D --> H[bridge.ts]
  H --> I[SharedWorker]

  I --> J[RequestTracker]
  I --> K[NetworkPipeline]
  K --> L[Provider plugin: Google Drive]

  L --> M[Domain event]
  M --> H
  H --> N[background.ts]
  N --> O[Log / Persist / Forward]
Loading

Framelink is an MV3 browser extension prototype for high-performance network interception across all tabs and frames.

The goal is to provide a clean foundation for browser-native monitoring products such as DLP, insider-risk, sharing controls, or SaaS activity detection without coupling low-level interception to provider-specific logic.

Why this project exists

Modern browser products often need to:

  • observe network traffic initiated from many tabs at once
  • capture activity from nested frames, not only top-level pages
  • normalize fetch and XMLHttpRequest into one pipeline
  • keep detection logic modular per provider or SaaS app
  • forward only high-value domain events to the extension runtime

Framelink implements that split.

Core idea

Framelink separates the system into two layers:

  1. Extension transport/runtime

    • injects the page patcher
    • creates the hidden bridge iframe
    • connects page contexts to one shared worker
    • forwards emitted domain events to the extension background
  2. Interception and provider packages

    • track request/response lifecycles
    • expose a normalized protocol
    • run provider hooks on completed transactions
    • emit typed domain events instead of raw traffic

This architecture keeps the hot path small while making provider logic easy to evolve.

Current status

This repository is a focused prototype with:

  • page-level interception for fetch and XMLHttpRequest
  • support for top-level documents and subframes
  • a shared-worker-based aggregation point
  • a pluggable interception pipeline
  • one sample provider: Google Drive sharing detection

When a matching Google Drive sharing request is detected, the worker emits a domain event and the background script receives it.

Architecture

src/
  background.ts       # receives domain events from the worker bridge
  bridge.ts           # hidden iframe runtime that talks to SharedWorker
  bridge.html         # iframe document
  content.ts          # per-frame bootstrap and page/bridge connection
  page.ts             # page-world patching of fetch/XHR
  manifest.json       # MV3 manifest and interception host config
  shared-worker.ts    # cross-tab processing hub

packages/
  interception-core/
    src/
      blob.ts         # safe Blob helpers
      pipeline.ts     # request/response hook execution
      protocol.ts     # transport protocol and normalized message types
      request-tracker.ts
      types.ts        # core plugin and event contracts

  provider-google-drive/
    src/
      index.ts
      parse-sharing.ts
      sharing-hook.ts
      types.ts

Runtime flow

Page frame
  -> content script decides whether interception is enabled
  -> injects page.js into the page world
  -> injects hidden bridge iframe
  -> connects both sides with MessageChannel

page.js
  -> patches fetch and XMLHttpRequest
  -> emits normalized request/response messages

bridge iframe
  -> owns the SharedWorker connection
  -> forwards page ports to the worker

shared worker
  -> tracks request/response pairs
  -> runs provider hooks
  -> emits high-level domain events

background service worker
  -> receives domain events
  -> persists / forwards / alerts / integrates with product logic

Package responsibilities

src/

Chrome-extension-specific runtime only.

Responsibilities:

  • bootstrap in every frame
  • apply host-based enablement rules
  • bridge page-world traffic into extension-owned infrastructure
  • forward final domain events to background logic

packages/interception-core

Reusable interception primitives.

Responsibilities:

  • normalized transport types
  • request/response correlation
  • provider hook execution
  • event sink abstraction

This package should stay generic and product-agnostic.

packages/provider-google-drive

Provider-specific business logic.

Responsibilities:

  • match Google Drive network patterns
  • parse sharing requests
  • emit google-drive.sharing-detected

Future providers should follow the same shape.

Why SharedWorker

Using a SharedWorker gives Framelink a single processing point shared by multiple tabs and frames from the same browser profile session.

That is useful for:

  • reducing duplicated per-tab processing logic
  • keeping provider pipelines centralized
  • making cross-tab aggregation possible
  • isolating heavy parsing away from the page context

The hidden iframe exists because extension/page/runtime boundaries do not let the page directly own the worker path Framelink needs.

Interception configuration

Host targeting is configured in src/manifest.json under the custom framelink.interception field:

{
  "framelink": {
    "interception": {
      "topLevelHosts": ["drive.google.com"],
      "frameHosts": ["drive.google.com"]
    }
  }
}

Only matching pages/frames enable the page patching path.

Event model

The pipeline is designed to emit domain events, not raw low-level packets.

Example event shape:

{
  type: 'google-drive.sharing-detected',
  provider: 'google-drive',
  payload: {
    fileId: '...',
    role: 'reader',
    permissionType: 'anyone',
    isAnyonePermission: true,
    requestUrl: '...',
    responseStatus: 200
  }
}

This is a better integration contract for DLP-style systems than exposing every raw request to downstream consumers.

Adding a new provider

Create a new package under packages/ and export a ProviderPlugin.

Minimal shape:

import type { ProviderPlugin } from '../interception-core/src/types';

export const myProvider: ProviderPlugin = {
  id: 'my-provider',
  hooks: [
    {
      id: 'my-provider/my-hook',
      matches(tx) {
        return tx.request.url.includes('/some/api');
      },
      async onComplete(tx, context) {
        await context.emit({
          type: 'my-provider.activity',
          provider: 'my-provider',
          payload: { url: tx.request.url }
        });
      }
    }
  ]
};

Then register it in src/shared-worker.ts.

Development

Install

npm install

Build

npm run build

Watch

npm run watch

Test

npm run test

Loading the extension

  1. Build the project.
  2. Open chrome://extensions.
  3. Enable Developer mode.
  4. Click Load unpacked.
  5. Select the dist/ directory.

Current limitations

This is still an early prototype.

Notable limitations:

  • only fetch and XMLHttpRequest are patched
  • response/error handling is intentionally minimal
  • request bodies are captured as Blobs with lightweight conversion
  • only one provider is implemented
  • host targeting is manifest-driven, not dynamically managed yet
  • no persistence, policy engine, or backend transport is included yet

Suggested next steps

If this evolves toward a production DLP architecture, likely next areas are:

  • provider registry and dynamic loading strategy
  • stronger event schemas and versioned contracts
  • batching/backpressure controls in the worker
  • richer request metadata and header capture
  • error/abort tracking
  • benchmark suite for high-tab/high-frame workloads
  • policy evaluation and sink plugins
  • enterprise-safe storage and export pipeline

Summary

Framelink is a compact but opinionated foundation for:

  • cross-tab, cross-frame browser interception
  • provider-oriented detection packages
  • high-value domain event emission
  • future DLP-style product architecture

It is not just an extension demo; it is a starting point for a modular browser telemetry and control plane.

About

Prototype Chrome extension (MV3) for high-performance network interception across tabs and frames using SharedWorker, hidden iframes, and provider-based DLP-style detection.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages