AWS Builder Center
Catching PII in the Browser, Convert to Data lake

Catching PII in the Browser, Convert to Data lake

A browser-native Chrome extension that detects PII locally using WebGPU inference before it leaves the tab, then ingests only structured findings into an encrypted S3 data lake with an organization-level dashboard for security teams.

Catching PII in the Browser, Before It Leaves the Tab

by Michael Shapira | Amazon Web Services
Status: Proof of concept. This project is a concept demo. It is not a production ready product. Every feature you see in the extension's options is fully functional today (per URL policies, automatic triggers, redaction, the upload pipeline, the Athena views). What is not in place for an enterprise rollout is SSO with a corporate identity provider and organization level control over the extension itself, meaning who is allowed to see and change the options. Both are described below as a forward looking direction rather than a deployment blueprint. The redaction (masking) feature is also experimental with known edge cases. There is a dedicated section for it below.

A user pastes a customer's Social Security number into a public chat assistant. By the time your DLP proxy sees it, your data lake scanner sees it, or your SIEM sees it, the data has already left your environment. The most sensitive moment in modern work is the half second between a person typing something and hitting Enter on a third party SaaS, and that is exactly the moment your existing controls cannot reach.
That gap is what this proof of concept tries to close, without becoming a new surveillance point on the way. Inference runs on the user's device. Page content never leaves the browser. The only thing that travels to AWS is a small structured finding (category, confidence, URL, timestamp).
This post walks through the PII Detection Chrome Extension , an open source Manifest V3  extension paired with a minimal AWS CDK  app. The openai/privacy-filter  model runs locally in the browser through WebGPU . Only findings are uploaded, into an encrypted S3 data lake that you can query with Athena .

Where This Fits Against AWS Native Data Protection

Amazon Macie  classifies PII at rest in S3. Amazon Comprehend  detects PII server side. AWS Network Firewall  and AWS Verified Access  inspect at the network edge. None of those see what a user is currently typing into a SaaS tab.
A network proxy could see it, but at a real cost. Now the proxy reads everything you do, every page, every prompt, every chat. For a lot of organizations, that level of visibility is itself a control they would rather not own.
The extension fills the remaining slot. The browser endpoint layer, with local only inference so nothing leaves the device unless a finding is generated.

Two Modes: Demo (Today) vs. Enterprise (What's Still Missing)

The same code base supports two operating models. The detection, redaction, policy, and upload features all work today in both modes. The difference between them is who controls the extension and how the user signs in.
What is implemented end to end in this PoC:
  • Local WebGPU inference through the offscreen document.
  • Per URL scan policies in DynamoDB, including automatic triggers (navigation, accumulate, form_submit).
  • Inline redaction (off by default, see the dedicated section).
  • The S3 + Athena data lake and the 10 pre built views.
  • Cognito user pool sign in with username and password.
What is not implemented and would need to be built for an enterprise rollout:
  • SSO with a corporate identity provider. Today the user signs in to a Cognito user pool directly. A real enterprise deployment would federate Cognito with the company's existing IdP (Okta, Microsoft Entra, Ping, Google Workspace) so the user gets a silent SSO flow, the same one they use for everything else, instead of a separate username and password.
  • Organization level control over the extension. Today the options page is open to whoever has the extension installed. Any user can flip triggers, change the operating point, toggle redaction, or disable the upload. In an enterprise rollout, an administrator would lock those options through Chrome Enterprise policy , so end users see only the controls IT chooses to expose, and per URL behavior is governed centrally rather than per machine.

Demo Mode, what the PoC actually does

A developer or a curious user installs the unpacked extension, signs in to a Cognito user pool with username and password, and uses every feature exactly as built. Manual triggers, automatic triggers, redaction, the policy table, the data lake, all of it works. This is the experience you get if you follow the Getting Started steps further down.

Enterprise Mode, what would need to be built

In a hypothetical enterprise rollout, the extension would be pushed to managed Chrome browsers through Chrome Enterprise policy and the user would never see a Cognito login screen. Authentication would be silent, federated through the corporate IdP into a Cognito identity pool. The options page would be locked down so end users cannot weaken settings, and per URL policy management would move from "anyone with AWS CLI" to a controlled admin surface.
Building that out is a real project. Cognito IdP federation, Chrome Enterprise policy authoring, MDM rollout, change management, user communication, legal review. None of it is in this repository. Treat this section as a description of a direction.
Demo Mode (PoC, today)Enterprise Mode (what's missing)
Sign inCognito username and password (works)Silent SSO via corporate IdP (not implemented)
Who can change optionsWhoever has the extension installedAdministrator, through Chrome Enterprise policy (not implemented)
Detection, redaction, policy table, upload, AthenaFully functionalFully functional
DistributionLoad unpacked from dist/Managed Chrome policy push (not implemented)

How It Works (Demo Mode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──────────────────────────── Browser ────────────────────────────┐
│ Content Script ──► Service Worker ──► Offscreen Document │
│ • DOM extract • orchestration • WebGPU inference │
│ • redaction • PKCE auth • privacy-filter │
│ • manual triggers • S3 upload │
│ • policy polling │
└─────────────────────────────────────────────────────────────────┘
│ (HTTPS, SSE-KMS)
▼
┌────────────────────────────── AWS ──────────────────────────────┐
│ Cognito ─► Identity Pool ─► STS ─► auth role ─► S3 (SSE-KMS) │
│ │ │
│ Policy API (API GW + Lambda) ─► DynamoDB ▼ │
│ Glue + Athena │
└─────────────────────────────────────────────────────────────────┘
The service worker creates a Chrome offscreen document  lazily on the first scan. The offscreen document is the only place WebGPU is available in MV3, so all inference lives there. Findings are serialized as 10 column Apache Parquet  and uploaded to S3 with SSE-KMS under a customer managed CMK. The bucket policy denies any PUT that omits the SSE-KMS header, so a misconfigured client fails closed instead of silently writing plain text.
The 10 Parquet columns are finding_id, user_id, scan_timestamp, source_url, page_title, pii_category, matched_text, confidence_score, operating_point, and extension_version. Athena reads them through a Glue table with partition projection  on year, month, and day. Ten pre built views answer the most common SecOps and reporting questions out of the box.
There is a precision and recall dial with three settings. high_precision (+2.0), balanced (0.0, the default), and high_recall (-2.0). It biases the model's Viterbi decoder  so the same model can serve a compliance audit and an incident response without retraining.

The DynamoDB Policy Table: Different Pages, Different Behavior

A single static scan strategy does not fit real usage. A CRM dashboard renders all the sensitive data up front, and you want to scan once on page load. A chat app drips data in one keystroke at a time, and scanning each keystroke is wasteful and inaccurate. You want to wait until a sentence has been typed. A page nobody cares about should only be scanned when the user explicitly asks.
The extension reads its per URL behavior from a DynamoDB table named pii-scan-policies, served through a Cognito authorized API Gateway  and Lambda . The extension polls on navigation and caches each URL's policy for 15 minutes, so changes propagate without an extension update.
Each item is keyed by a urlPattern (glob) and carries a trigger, an enabled flag, and an optional description. The supported triggers are:
  • manual. User initiated only. The default, no policy needed.
  • navigation. Scan once when the page finishes loading. Right for pages that render their sensitive data up front.
  • accumulate. Buffer typed text per tab and flush a single scan after a quiet window or a character threshold. Right for chat surfaces where the interesting content is what the user is composing.
  • form_submit. Scan when a form is submitted.
Two worked examples:
  • Salesforce, scan on page load. A CRM renders customer records as soon as the page is done loading. One scan on navigation catches everything visible, without re scanning on every micro interaction.
1
2
3
4
5
6
aws dynamodb put-item --table-name pii-scan-policies --item '{
"urlPattern": {"S": "*.salesforce.com"},
"trigger": {"S": "navigation"},
"enabled": {"BOOL": true},
"description": {"S": "Salesforce CRM, scan once when the page finishes loading"}
}
  • WhatsApp Web, accumulate typed text and then scan. Scanning each keystroke is noisy and inaccurate. accumulate waits for a pause or a character threshold so the model sees a complete thought instead of fragments.
    1
    2
    3
    4
    5
    6
    aws dynamodb put-item --table-name pii-scan-policies --item '{
    "urlPattern": {"S": "web.whatsapp.com"},
    "trigger": {"S": "accumulate"},
    "enabled": {"BOOL": true},
    "description": {"S": "WhatsApp Web, buffer typed text and scan after the user pauses"}
    }'
A platform team can add, retune, or disable a policy with one CLI call. No extension redeployment, no client side rollout. In Demo Mode this policy table is optional. The extension falls back to manual triggers when no policy matches. In a hypothetical Enterprise Mode it would be the primary control plane.

Redaction Is Experimental, Read This Before Enabling It

Inline redaction (masking detected spans in place with a length preserving character) is the most user visible feature, and the most fragile one. It is off by default and should be treated as experimental.
What works well today: static pages, plain text nodes, simple form fields, and SPAs that re render predictably. A MutationObserver  re applies masks when Gmail or Slack redraw their DOM.
Known edge cases where masking can misbehave:
  • Closed Shadow DOM and cross origin iframes are not traversed on purpose. PII inside them will not be detected or masked.
  • Canvas, WebGL, and PDF viewers render text as pixels rather than DOM, so they are out of reach.
  • Virtualized lists (long chat histories, large tables) recycle DOM nodes. Masks can briefly disappear when the host app reuses a node for different content before the observer catches up.
  • Heavy contenteditable editors (Google Docs, Notion, rich text composers) manage their own selection and undo stacks. Masking inside them can fight the editor's internal state, which is why it is gated behind an explicit opt in (alsoRedactInputFields).
  • False positives are still visible to the user. Masking does not remove text from the DOM in any unrecoverable way. An in memory undo stack restores the original on the current tab. During the masked window the user does see a placeholder.
The redaction path is verified by a property test that intercepts fetch, XMLHttpRequest, sendBeacon, and WebSocket and asserts that none of them are called during masking. Even an experimental redaction will not quietly exfiltrate data. Detection accuracy and infrastructure encryption are far more mature than this UI behavior. If you only need a signal, run with redaction off.

Security Posture (Within the Limits of a PoC)

  • Local inference. Model weights and page text never leave the device. Only structured findings are uploaded.
  • Encryption everywhere. S3 SSE-KMS under a customer managed CMK, bucket policy denies non KMS PUTs, tokens AES-GCM encrypted in chrome.storage.local with a non extractable key derived through HKDF-SHA256.
  • Least privilege IAM. The authenticated Cognito role grants exactly s3:PutObject on the date partitioned findings prefix, conditioned on SSE-KMS, plus kms:GenerateDataKey on the findings CMK. The policy Lambda is scoped to dynamodb:GetItem and dynamodb:Query on pii-scan-policies only.
  • No third party fetches at scan time. ONNX Runtime WASM files are bundled, not pulled from a CDN. The MV3 service worker uses only static import statements.
  • Verified invariants. 19 named correctness properties run at least 100 randomized iterations each through fast-check . Redaction is length preserving, redact + undo is the identity, the redaction path emits zero network traffic, S3 PUTs always carry SSE-KMS, and Parquet round trips deep equal across all 10 columns.
These are sound foundations for a concept, not a substitute for a security review of a production deployment. The CDK app uses RemovalPolicy.DESTROY on the bucket, CMK, and DynamoDB table to make experimentation easy. That alone disqualifies it from a production rollout without modification.

Getting Started (Demo Mode)

Requires Node.js 20+, npm 10+, and the AWS CDK CLI v2 .
1
2
3
4
5
6
7
8
9
10
11
git clone https://github.com/example-org/pii-detection-chrome-extension.git
cd pii-detection-chrome-extension/infra
npm install
npx cdk bootstrap
INITIAL_USER_EMAIL=you@example.com npm run deploy

cd ../extension
cp .env.example .env # fill in CDK outputs: Cognito, S3, KMS, Policy API
npm install
npm run build
# chrome://extensions → Developer mode → Load unpacked → select extension/dist
Sign in with the seeded user, visit any page, and click the toolbar icon. Findings appear in the popup and land in the S3 data lake within seconds.

Demo

Recorded demo in this linkidin post linkidin post 

Closing

The browser endpoint layer is the one place existing AWS data protection services do not reach, and a network proxy is a heavy answer for a narrow question. A local first model, a tiny structured upload, and a per URL policy table can give a security team the signal it actually wants (did sensitive data appear on this user's screen?) without giving anyone a new firehose of page content to manage.
Demo Mode works today and is enough to play with the idea on your own machine. Detection, redaction, the per URL policy table, the upload pipeline, and the Athena views are all fully functional. What is missing for an enterprise rollout is silent SSO into a corporate IdP and admin level control over the extension's options. Both are described above as a direction rather than a finished capability. Everything else, including the local inference loop, is the part this PoC is meant to demonstrate.
The code is open source. Try it, break it, file issues, and tell me where the edges hurt. The repository lives at https://github.com/MichaelShapira/pii_at_the_edge .

About the Author

Michael Shapira is a solutions architect at Amazon Web Services based in Tel Aviv, Israel, working on generative AI and cloud native architectures.

Further Reading

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article