extensionguard 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. extensionguard-0.3.0.dist-info/METADATA +736 -0
  2. extensionguard-0.3.0.dist-info/RECORD +50 -0
  3. extensionguard-0.3.0.dist-info/WHEEL +5 -0
  4. extensionguard-0.3.0.dist-info/entry_points.txt +9 -0
  5. extensionguard-0.3.0.dist-info/licenses/LICENSE +21 -0
  6. extensionguard-0.3.0.dist-info/top_level.txt +1 -0
  7. extguard/__init__.py +19 -0
  8. extguard/__main__.py +6 -0
  9. extguard/adapters/__init__.py +0 -0
  10. extguard/adapters/http_retry.py +117 -0
  11. extguard/adapters/pagerduty.py +197 -0
  12. extguard/adapters/sentinel.py +191 -0
  13. extguard/adapters/slack.py +223 -0
  14. extguard/adapters/splunk.py +101 -0
  15. extguard/alert_dispatcher.py +706 -0
  16. extguard/behavioral_monitor.py +1427 -0
  17. extguard/claude_triage.py +366 -0
  18. extguard/code_diff.py +375 -0
  19. extguard/config_schema.py +234 -0
  20. extguard/crx_parser.py +399 -0
  21. extguard/dashboard.py +530 -0
  22. extguard/dashboard_static/style.css +146 -0
  23. extguard/dashboard_templates/base.html +29 -0
  24. extguard/dashboard_templates/case.html +126 -0
  25. extguard/dashboard_templates/error.html +9 -0
  26. extguard/dashboard_templates/index.html +86 -0
  27. extguard/dashboard_templates/verify_result.html +43 -0
  28. extguard/logging_setup.py +237 -0
  29. extguard/main.py +811 -0
  30. extguard/models.py +85 -0
  31. extguard/osv_lookup.py +331 -0
  32. extguard/paths.py +102 -0
  33. extguard/permission_scorer.py +423 -0
  34. extguard/publisher_checker.py +499 -0
  35. extguard/remediation.py +726 -0
  36. extguard/remediators/__init__.py +0 -0
  37. extguard/remediators/chrome_killer.py +596 -0
  38. extguard/remediators/cred_rotation.py +578 -0
  39. extguard/remediators/forensics.py +468 -0
  40. extguard/sigma_generator.py +450 -0
  41. extguard/ttp_ingestor.py +647 -0
  42. extguard/ttp_library/README.md +36 -0
  43. extguard/ttp_library/campaigns/shai_hulud.md +39 -0
  44. extguard/ttp_library/campaigns/teamccp_nx_console.md +33 -0
  45. extguard/ttp_library/mitre_reference.md +18 -0
  46. extguard/ttp_library/patterns/credential_chains.md +45 -0
  47. extguard/ttp_loader.py +173 -0
  48. extguard/update_velocity.py +310 -0
  49. extguard/virustotal_lookup.py +231 -0
  50. extguard/webhook_server.py +244 -0
@@ -0,0 +1,736 @@
1
+ Metadata-Version: 2.4
2
+ Name: extensionguard
3
+ Version: 0.3.0
4
+ Summary: Defensive SOC tool for browser extension supply chain threat detection
5
+ Author: ExtensionGuard contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Vimal7747/extensionguard
8
+ Project-URL: Issues, https://github.com/Vimal7747/extensionguard/issues
9
+ Keywords: security,chrome,extension,soc,supply-chain,mitre
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Information Technology
12
+ Classifier: Topic :: Security
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: anthropic>=0.40.0
24
+ Requires-Dist: requests>=2.31.0
25
+ Requires-Dist: websockets>=12.0
26
+ Requires-Dist: flask>=3.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8.0; extra == "dev"
29
+ Requires-Dist: pytest-mock>=3.12; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
31
+ Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.5; extra == "dev"
33
+ Requires-Dist: build>=1.0; extra == "dev"
34
+ Provides-Extra: workspace
35
+ Requires-Dist: google-api-python-client>=2.100; extra == "workspace"
36
+ Requires-Dist: google-auth>=2.30; extra == "workspace"
37
+ Dynamic: license-file
38
+
39
+ # ExtensionGuard
40
+
41
+ > Defensive SOC tooling for detecting, alerting on, and remediating malicious browser-extension supply-chain attacks.
42
+
43
+ ExtensionGuard is a Python pipeline that catches malicious Chrome extensions
44
+ before they exfiltrate credentials. It's built for blue-team / SOC use,
45
+ inspired by the May 2026 TeamPCP supply-chain breach — a compromised npm
46
+ publisher account that pushed a malicious update to the legitimate Nx Console
47
+ extension and harvested GitHub / npm session tokens from every install.
48
+
49
+ What's in the box:
50
+
51
+ - **Pre-install scan** — hardened CRX parser (zip-bomb limits, malformed
52
+ manifests can't crash it), TTP-calibrated permission scorer, signing
53
+ identity + Chrome Web Store build verification (byte-for-byte hash match),
54
+ VirusTotal file-hash lookup, OSV check of bundled npm packages,
55
+ version-velocity detection, and a code diff against the last accepted
56
+ build that catches hijacked updates shipped as an ordinary patch bump.
57
+ - **AI triage** — Claude with a cached threat-intel library, forced
58
+ structured output, and an injection-resistant prompt. Claude can raise a
59
+ verdict but never lower it: the final score is the higher of Claude's and
60
+ the deterministic Stage 1 score.
61
+ - **Runtime monitor** — Chrome DevTools Protocol sensor that instruments
62
+ extension service workers, extension pages and content scripts. Seven
63
+ rules (see [Running the runtime monitor](#running-the-runtime-monitor)).
64
+ Tested end to end against a real headless Chrome.
65
+ - **SOC alert dispatch** — parallel fan-out to Sentinel, Splunk, PagerDuty,
66
+ and Slack with deduplication, escalation, and retry/backoff.
67
+ - **Remediation** — forensic preservation of the original sample with an
68
+ HMAC-signed chain of custody. Blocks the extension through the Windows
69
+ registry, the Linux policy file, a macOS configuration profile, or the
70
+ Google Workspace Chrome Policy API. Also generates a credential-rotation
71
+ playbook. Policy changes need explicit confirmation.
72
+ - **Analyst dashboard** — Flask web UI to browse quarantine cases, verify
73
+ evidence integrity, and approve / reject remediation actions (executed by
74
+ `extguard-remediate --process-queue`).
75
+ - **Live threat-intel updates** — a separate webhook receiver
76
+ (`extguard-webhook`, HMAC-verified, replay-protected) keeps the TTP library
77
+ in sync from a central repo. Updates are staged and only go live after an
78
+ analyst activates them.
79
+ - **SIEM portability** — Sigma rules for proxy logs and for the alerts
80
+ ExtensionGuard forwards to Splunk, Sentinel, Elastic, or Chronicle.
81
+
82
+ ## Status
83
+
84
+ | Property | Value |
85
+ | --- | --- |
86
+ | Version | **0.3.0** |
87
+ | Tests | **829 passing** (`pytest`) + 5 opt-in live tests (real APIs, headless Chrome) |
88
+ | Benchmarks | **13** in `tests/benchmarks/` (see [PERFORMANCE.md](PERFORMANCE.md)) |
89
+ | Lint | **0 findings** (`ruff check`) |
90
+ | Python | 3.10 – 3.13 |
91
+ | Runtime deps | `anthropic`, `requests`, `websockets`, `flask` |
92
+ | Optional deps | `[dev]`, `[workspace]` |
93
+ | Console scripts | 8 (see below) |
94
+
95
+ Companion docs:
96
+
97
+ - [CHANGELOG.md](CHANGELOG.md) — what changed between releases
98
+ - [THREAT_MODEL.md](THREAT_MODEL.md) — trust boundaries + 13 catalogued threats
99
+ - [PERFORMANCE.md](PERFORMANCE.md) — measured baselines for every hot path
100
+ - [RELEASING.md](RELEASING.md) — release checklist for maintainers
101
+
102
+ ---
103
+
104
+ ## Quickstart
105
+
106
+ ### From source
107
+
108
+ ```powershell
109
+ git clone https://github.com/Vimal7747/extensionguard.git
110
+ cd extensionguard
111
+ pip install -e ".[dev]"
112
+ ```
113
+
114
+ ### From PyPI (once published)
115
+
116
+ ```powershell
117
+ pip install extensionguard
118
+ ```
119
+
120
+ ### From Docker
121
+
122
+ ```powershell
123
+ docker compose up -d # full SOC deployment
124
+ # OR
125
+ docker run -p 127.0.0.1:5000:5000 extensionguard:0.3.0 # dashboard, localhost only
126
+ ```
127
+
128
+ ### Run a scan
129
+
130
+ ```powershell
131
+ # Scan a sample malicious manifest (no API key needed)
132
+ extguard test_fixtures/teamccp_sim_manifest.json --no-ai --offline
133
+
134
+ # With Claude AI triage
135
+ $env:ANTHROPIC_API_KEY = "sk-ant-..."
136
+ extguard test_fixtures/teamccp_sim_manifest.json
137
+
138
+ # Machine-readable for SIEM / webhook ingestion
139
+ extguard test_fixtures/teamccp_sim_manifest.json --json
140
+
141
+ # Full incident-response pipeline against a triage result
142
+ extguard test_fixtures/teamccp_sim_manifest.json --no-ai --offline --json > triage.json
143
+ extguard-remediate --from-triage triage.json --dry-run --pd-action none
144
+
145
+ # Browse quarantine cases, verify integrity, approve remediations
146
+ extguard-dashboard # http://127.0.0.1:5000
147
+ extguard-remediate --process-queue # execute what analysts approved
148
+
149
+ # Pull latest threat intel from a GitHub repo, review it, make it live
150
+ extguard-ttp-sync --owner myorg --repo extguard-ttp
151
+ extguard-ttp-sync --activate
152
+
153
+ # Export detections as Sigma rules for Splunk / Sentinel / Elastic
154
+ extguard-sigma --output sigma/
155
+ ```
156
+
157
+ ### Verdicts, exit codes and automation
158
+
159
+ - **Final score** = the higher of the Stage 1 composite score and Claude's
160
+ score. A file that 11+ VirusTotal engines flag is raised to at least 90.
161
+ An update that starts sending data to exfil-style hosting (Discord /
162
+ Telegram webhooks, `*.workers.dev`, tunnels) that the previous accepted
163
+ build never used is raised to at least 60 (HIGH).
164
+ - **Baselines.** Each LOW / MEDIUM scan is saved as the reference for
165
+ comparing the next version: its version for velocity, its code profile for
166
+ the code diff. HIGH / CRITICAL builds never become the baseline. Nor does a
167
+ build whose code changed (new endpoints, sensitive APIs, obfuscation) until
168
+ you review it and re-scan with `--accept-baseline`. `--no-record` saves
169
+ nothing.
170
+ - `--json` always prints one JSON document, including when the AI stage is
171
+ skipped or fails (`ai_triage.status`) and when the scan can't complete
172
+ (`{"error": ..., "stage": ...}`).
173
+ - `--offline` makes no network calls at all: no Web Store, VirusTotal, OSV,
174
+ or Claude API.
175
+ - Exit codes: `0` done · `1` verdict at or above `--fail-on` · `2` bad
176
+ arguments · `3` the scan could not be completed.
177
+
178
+ ```powershell
179
+ extguard suspicious.crx --json --fail-on high # exit 1 if HIGH or CRITICAL
180
+ ```
181
+
182
+ ### Where ExtensionGuard keeps data
183
+
184
+ Everything lives under `EXTGUARD_HOME` (default `~/.extguard`): `quarantine/`,
185
+ the active `ttp_library/` and the staged `ttp_library.pending/`,
186
+ `remediation_queue.jsonl` (+ `.done`), `version_history.json`,
187
+ `code_profiles/`, `webhook_deliveries.json`, and the evidence signing key
188
+ `coc_hmac.key`. `EXTGUARD_QUARANTINE` and `EXTGUARD_TTP_DIR` override the
189
+ quarantine and TTP locations.
190
+
191
+ `extguard.conf.json` is found in this order: `--config`, `$EXTGUARD_CONFIG`,
192
+ `./extguard.conf.json`, `$EXTGUARD_HOME/extguard.conf.json`.
193
+
194
+ ### Evidence integrity
195
+
196
+ Stage 5 preserves the **original** sample file, so its SHA-256 matches what
197
+ VirusTotal and the Web Store know it by. `chain_of_custody.json` is signed
198
+ with HMAC-SHA256 using a key kept outside the case folder: set
199
+ `EXTGUARD_COC_KEY` (for example from a secrets manager), or let ExtensionGuard
200
+ create `~/.extguard/coc_hmac.key`. `extguard-remediate --verify-case <dir>`
201
+ fails if an artifact or the chain of custody was edited, or the signature is
202
+ missing. Anyone who can read the key can still forge a signature, so keep it
203
+ away from the analyst workstation account where you can.
204
+
205
+ Expected output for the malicious test fixture:
206
+
207
+ ```
208
+ Stage 1 composite: 100/100 - CRITICAL
209
+ Flagged: webRequestBlocking, cookies, webRequest, tabs, history, storage, <all_urls>
210
+ [!] Session token harvesting combo - matches TeamPCP TTP (T1555.003)
211
+ [!] Traffic intercept + cookie theft pipeline (T1071 + T1555)
212
+ [!] Persistent background page - always-on monitoring
213
+ Recommendation: BLOCK IMMEDIATELY - initiate remediation playbook
214
+ ```
215
+
216
+ ### Running the runtime monitor
217
+
218
+ The monitor drives Chrome through the DevTools Protocol. **Anything that can
219
+ reach the debugging port has full control of that browser** - cookies,
220
+ sessions, every tab. So run it against a separate sandbox Chrome with its own
221
+ profile, never your everyday one (Chrome 136+ refuses remote debugging on the
222
+ default profile anyway):
223
+
224
+ ```powershell
225
+ & "C:\Program Files\Google\Chrome\Application\chrome.exe" `
226
+ --user-data-dir=C:\extguard-sandbox --remote-debugging-port=9222
227
+ extguard-monitor --list-targets # find extension IDs
228
+ extguard-monitor --output-json | extguard-dispatch # all extensions -> SOC
229
+ extguard-monitor --ext-id <id> --snapshot-storage <id> --out storage.json
230
+ ```
231
+
232
+ It attaches to extension service workers, extension pages and web pages (for
233
+ content scripts; `--no-pages` skips them). It hooks the storage, management
234
+ and cookie APIs before the extension's own code runs, and reconnects if
235
+ Chrome restarts (`--once` to exit instead).
236
+
237
+ | Rule | Detects | Severity |
238
+ | --- | --- | --- |
239
+ | RULE-01 | POST to exfil-style hosting (`*.workers.dev`, tunnels...); regular beaconing | high / critical |
240
+ | RULE-02 | Tokens, passwords or cookie dumps sent to a host they don't belong to | high / critical |
241
+ | RULE-03 | Calls to GitHub / npm / cloud / SSO APIs; authenticated state-changing requests | medium / high |
242
+ | RULE-04 | Large encoded blobs or credentials staged in extension storage | high / critical |
243
+ | RULE-05 | `chrome.management` used to disable or uninstall another extension | critical |
244
+ | RULE-06 | `eval` / `new Function` / injected remote scripts; obfuscated eval chains | medium / high |
245
+ | RULE-07 | `chrome.cookies.getAll` bulk reads; `document.cookie` reads on high-value sites | medium / high |
246
+
247
+ ### Remediation
248
+
249
+ ```powershell
250
+ extguard-remediate --from-triage triage.json --crx sample.crx # asks you to type BLOCK
251
+ extguard-remediate --ext-id <id> --crx sample.crx --yes # automation / SOAR
252
+ extguard-remediate --from-triage triage.json --workspace-ou /Engineering
253
+ extguard-remediate --from-triage triage.json --alerts-log alerts.jsonl `
254
+ --storage-snapshot storage.json # more evidence
255
+ ```
256
+
257
+ - **Windows / Linux** write Chrome's `ExtensionInstallBlocklist` policy (run
258
+ as administrator / root). It applies when Chrome reloads policy.
259
+ - **macOS**: Chrome only honours *managed* preferences, so ExtensionGuard
260
+ writes `extguard-block-<id>.mobileconfig`. Install it through your MDM or
261
+ System Settings > Privacy & Security > Profiles. The step is reported as
262
+ PARTIAL until then.
263
+ - **Google Workspace**: `--workspace-ou` blocks the extension for an org unit
264
+ via the Chrome Policy API. The request follows Google's documented format;
265
+ it has not been run against a live tenant from this project.
266
+ - **PagerDuty** is *acknowledged* by default, not resolved. Blocking doesn't
267
+ undo stolen credentials, so resolve it with `--pd-action resolve` after the
268
+ rotation playbook is done.
269
+ - Exit code `1` means a step failed or was refused.
270
+
271
+ ---
272
+
273
+ ## Architecture
274
+
275
+ Five loosely-coupled stages plus the dashboard. Each stage can run
276
+ standalone — you don't need a SOC platform to use just the pre-install
277
+ scanner.
278
+
279
+ ```
280
+ +----------------------------------------------------------------+
281
+ | Stage 1: Pre-install detection (CLI: extguard) |
282
+ | crx_parser - parses CRX2/CRX3/zip/manifest.json |
283
+ | permission_scorer - TTP-calibrated weight + combo rules |
284
+ | publisher_checker - extension ID + Web Store cross-check |
285
+ | osv_lookup - SHA-256 hash + npm dep vuln scan |
286
+ | virustotal_lookup - multi-engine consensus (optional) |
287
+ | update_velocity - semver jump detection vs history |
288
+ | code_diff - code changes vs last accepted build |
289
+ +----------------------------------------------------------------+
290
+ v (composite score + manifest)
291
+ +----------------------------------------------------------------+
292
+ | Stage 2: AI triage (claude_triage.py) |
293
+ | - Loads threat-intel from ttp_library/ on disk |
294
+ | - Prompt caching: TTP library reused per 5-min cache window |
295
+ | - Forced tool_use: Claude returns structured JSON, not prose |
296
+ | - Prompt-injection mitigation on every manifest field |
297
+ +----------------------------------------------------------------+
298
+ v (triage JSON)
299
+ +----------------------------------------------------------------+
300
+ | Stage 3: Runtime behavioral monitor (CLI: extguard-monitor) |
301
+ | - Connects to a sandbox Chrome over CDP (remote debugging) |
302
+ | - Instruments service workers, extension pages, content |
303
+ | scripts; hooks storage / management / cookie APIs |
304
+ | - Seven detection rules: |
305
+ | RULE-01 POST / beacon to exfil-style hosting |
306
+ | RULE-02 Credentials sent to a host they don't belong to |
307
+ | RULE-03 High-value API access / session riding |
308
+ | RULE-04 Data staged in extension storage |
309
+ | RULE-05 Extension disables / uninstalls another |
310
+ | RULE-06 Dynamic or obfuscated code execution |
311
+ | RULE-07 Bulk cookie reads |
312
+ | - Emits JSON alert lines on stdout |
313
+ +----------------------------------------------------------------+
314
+ v (alert JSON lines)
315
+ +----------------------------------------------------------------+
316
+ | Stage 4: SOC alert dispatch (CLI: extguard-dispatch) |
317
+ | - Dedup (300s TTL by fingerprint = rule + extension_id) |
318
+ | - Escalation (severity auto-promoted on Nth re-fire) |
319
+ | - Enrichment (sensor host, recommendation, MITRE) |
320
+ | - Parallel fan-out: |
321
+ | Microsoft Sentinel (HMAC-SHA256 LAW API) |
322
+ | Splunk HTTP Event Collector |
323
+ | PagerDuty Events API v2 |
324
+ | Slack Incoming Webhook |
325
+ | - Retry/backoff (3 attempts, 1s/2s/4s + jitter) on 5xx/429 |
326
+ +----------------------------------------------------------------+
327
+ v (paged analyst confirms threat)
328
+ +----------------------------------------------------------------+
329
+ | Stage 5: Remediation (CLI: extguard-remediate) |
330
+ | 1. PRESERVE - quarantine CRX + chain of custody + SHA-256 |
331
+ | 2. KILL - add extension ID to Chrome ExtensionInstall- |
332
+ | Blocklist: HKLM registry (Windows), |
333
+ | /etc/opt/chrome/policies (Linux), |
334
+ | .mobileconfig profile (macOS), |
335
+ | Chrome Policy API (Workspace) |
336
+ | 3. PLAYBOOK - generate credential-rotation runbook for |
337
+ | each at-risk store (GitHub, npm, AWS, Slack, |
338
+ | Atlassian, Google) |
339
+ | 4. PAGERDUTY - acknowledge (resolve on request) |
340
+ | 5. REPORT - console summary + verify command |
341
+ +----------------------------------------------------------------+
342
+
343
+ Sitting alongside all of the above:
344
+ +----------------------------------------------------------------+
345
+ | Analyst dashboard (CLI: extguard-dashboard, localhost only) |
346
+ | - Browses quarantine cases + verifies SHA-256 integrity |
347
+ | - Tails recent alerts |
348
+ | - Approve/reject queue (a file that |
349
+ | `extguard-remediate --process-queue` executes; the |
350
+ | dashboard never modifies system state itself) |
351
+ +----------------------------------------------------------------+
352
+ | TTP webhook (CLI: extguard-webhook, internet-facing) |
353
+ | - GitHub pushes, HMAC-SHA256 verified, replay-protected |
354
+ | - Stages the new library for `extguard-ttp-sync --activate` |
355
+ +----------------------------------------------------------------+
356
+ ```
357
+
358
+ ---
359
+
360
+ ## Console scripts
361
+
362
+ After `pip install`, eight CLIs are available:
363
+
364
+ | Command | Purpose |
365
+ | --- | --- |
366
+ | `extguard` | Stage 1 pre-install scanner |
367
+ | `extguard-monitor` | Stage 3 runtime behavioural monitor (CDP client) |
368
+ | `extguard-dispatch` | Stage 4 alert dispatcher (reads JSONL from stdin) |
369
+ | `extguard-remediate` | Stage 5 incident-response orchestrator |
370
+ | `extguard-dashboard` | Flask analyst UI on 127.0.0.1:5000 |
371
+ | `extguard-ttp-sync` | Pull the TTP library from GitHub (staged); `--status`, `--activate` |
372
+ | `extguard-webhook` | GitHub push receiver for TTP updates (separate from the dashboard) |
373
+ | `extguard-sigma` | Export detection rules as Sigma YAML for SIEM import |
374
+
375
+ ---
376
+
377
+ ## Configuration
378
+
379
+ A single config file controls all destinations and tunables. Copy the
380
+ template and edit:
381
+
382
+ ```powershell
383
+ copy extguard.conf.json my-config.json
384
+ extguard-dispatch --config my-config.json --source triage
385
+ ```
386
+
387
+ Top-level sections:
388
+
389
+ | Section | What it controls |
390
+ | --- | --- |
391
+ | `sentinel` | Workspace ID + base64 shared key for Log Analytics ingestion |
392
+ | `splunk` | HEC URL + token + index/sourcetype |
393
+ | `pagerduty` | Events v2 integration key, min severity for paging |
394
+ | `slack` | Incoming webhook URL, channel, min severity |
395
+ | `virustotal` | API key for Stage 1d multi-engine hash lookup |
396
+ | `webhook` | TTP repo coordinates, webhook secret, `auto_activate`, `require_verified_commit` |
397
+ | `workspace` | Service account, customer ID and admin email for the Chrome Policy API |
398
+ | `dispatch` | Dedup TTL, escalation threshold, global min severity |
399
+
400
+ Environment variables override credential defaults — preferred for production
401
+ because keys never end up in `extguard.conf.json` on disk:
402
+
403
+ | Variable | Purpose | Default |
404
+ | --- | --- | --- |
405
+ | `ANTHROPIC_API_KEY` | Required for Stage 2 AI triage | unset |
406
+ | `EXTGUARD_CLAUDE_MODEL` | Pin a specific Claude snapshot | `claude-sonnet-4-6` |
407
+ | `VT_API_KEY` | Enable VirusTotal hash lookup in Stage 1d | unset |
408
+ | `VT_DISABLED` | If `"1"`, globally disable VT (privacy opt-out) | unset |
409
+ | `GITHUB_TOKEN` | PAT for private TTP repos or higher rate limit | unset |
410
+ | `EXTGUARD_LOG_LEVEL` | `DEBUG`/`INFO`/`WARNING`/`ERROR` | `INFO` |
411
+ | `EXTGUARD_LOG_FILE` | Path to also write JSON log lines | unset |
412
+ | `EXTGUARD_LOG_JSON` | If `"1"`, stderr is JSON too | unset |
413
+
414
+ The config file itself is validated on load — missing required fields,
415
+ placeholder values, type mismatches, and bad severity strings produce a
416
+ clear error rather than a silent dispatch failure.
417
+
418
+ ---
419
+
420
+ ## Detection coverage
421
+
422
+ ### Known campaigns
423
+
424
+ | Campaign | Year | Pattern | What ExtensionGuard catches |
425
+ | --- | --- | --- | --- |
426
+ | **TeamPCP / Nx Console** | 2026 | Compromised npm publisher pushes malicious .crx | Permission combo (cookies+tabs+storage+`<all_urls>`), beacon to `*.workers.dev` |
427
+ | **Shai-Hulud family** | 2025-26 | Typosquatted extensions abuse `debugger` + `nativeMessaging` | Critical permission weights + Runtime obfuscated-eval detection |
428
+
429
+ ### MITRE ATT&CK techniques
430
+
431
+ T1176 (Browser Extensions), T1555.003 (Credentials from Web Browsers),
432
+ T1071.001 (Web Protocols / C2), T1059.007 (JavaScript), T1530 (Data from
433
+ Cloud Storage), T1074 (Data Staged), T1185 (Browser Session Hijacking).
434
+
435
+ ### Detection layers (pre-install)
436
+
437
+ 1. **Permission weights** — `debugger` and `nativeMessaging` are 35 points
438
+ each; `userScripts` 25; `cookies`, `scripting`, `webRequestBlocking`,
439
+ `webRequestAuthProvider`, tab / desktop capture, `management`, `proxy`
440
+ 20 each. Optional permissions count at half weight - an extension can
441
+ request them at any time.
442
+ 2. **Combo bonuses** — known attack-chain combos (e.g.
443
+ `cookies+tabs+storage` = TeamPCP session-token harvester; `scripting` on
444
+ all sites; `cookies` on high-value domains).
445
+ 3. **Host access** — every spelling of "all sites" (`<all_urls>`,
446
+ `*://*/*`, `https://*/*`, `*://*.com/*`...), high-value domains (code
447
+ hosting, package registries, cloud consoles, SSO), content scripts in the
448
+ page's MAIN world, `externally_connectable` to every site, and weak CSP.
449
+ 4. **Signing identity** — the extension ID is taken from the signed `crx_id`
450
+ in the CRX3 header (the value Chrome itself uses), with the manifest `key`
451
+ as a cross-check. A manifest key that contradicts the signature is flagged.
452
+ 5. **Chrome Web Store build check** — the store's update service reports the
453
+ current version and the SHA-256 of the exact `.crx` it serves. A matching
454
+ hash means "this is the genuine store build". The same version with a
455
+ different hash means a repackaged or tampered build.
456
+ 6. **VirusTotal file-hash lookup** (optional) — the SHA-256 of the whole
457
+ input file. Free tier: 4 req/min, 500/day. Set `VT_DISABLED=1` for teams
458
+ that can't send hashes to a third party.
459
+ 7. **OSV package check** — npm packages bundled in the extension (from
460
+ `package.json` / `package-lock.json`) checked against `api.osv.dev`.
461
+ (OSV has no file-hash lookup; an earlier version sent one anyway and it
462
+ always came back empty.)
463
+ 8. **Version velocity** — large semver jumps (e.g. 1.0.4 → 17.3.1) and
464
+ versions that go backwards, from local history.
465
+ 9. **Code diff (Stage 1f)** — endpoints, sensitive APIs (cookies, debugger,
466
+ `eval`, remote script loading...) and obfuscation, compared with the last
467
+ accepted build of the same extension. A hijacked update that ships as an
468
+ ordinary patch bump shows up as new endpoints / capabilities; one that
469
+ starts talking to exfil-style hosting is at least HIGH.
470
+
471
+ A lookup that fails (network error, rate limit) is reported as **unknown**,
472
+ never as clean.
473
+
474
+ ---
475
+
476
+ ## What it doesn't do
477
+
478
+ Honest list of out-of-scope items so you know when to reach for another
479
+ tool:
480
+
481
+ - **No machine learning / behavioural model training.** Detection is rules
482
+ + LLM-assisted triage, not anomaly detection over your fleet.
483
+ - **No browser-process memory inspection.** We see what CDP exposes; we
484
+ don't attach a kernel probe.
485
+ - **No Firefox / Safari / Edge support yet.** Extensions on other browsers
486
+ use different formats and APIs.
487
+ - **No automatic credential rotation.** The playbook generates step-by-step
488
+ instructions; an analyst still has to click the buttons (deliberate —
489
+ auto-rotating prod credentials is too risky).
490
+ - **No persistent SOC backend.** The dashboard is a thin Flask app reading
491
+ from disk; there's no database, multi-tenancy, or RBAC. Pair with your
492
+ existing SIEM for query history.
493
+ - **Single-host state.** Dedup / version-history / quarantine all live on
494
+ the local filesystem. For a multi-host fleet, run one sensor per host
495
+ and rely on destination-side dedup (PagerDuty's `dedup_key`, Sentinel
496
+ log-table queries).
497
+
498
+ ---
499
+
500
+ ## Project layout
501
+
502
+ ```
503
+ extguard/ (repository root)
504
+ extguard/ - the Python package (everything pip installs)
505
+ __main__.py - `python -m extguard` = `extguard`
506
+ paths.py - where config + data live (EXTGUARD_HOME)
507
+
508
+ # Console-script entry points
509
+ main.py - Stage 1 CLI (extguard)
510
+ behavioral_monitor.py - Stage 3 CDP monitor (extguard-monitor)
511
+ alert_dispatcher.py - Stage 4 dispatcher (extguard-dispatch)
512
+ remediation.py - Stage 5 orchestrator (extguard-remediate)
513
+ dashboard.py - Flask web UI (extguard-dashboard)
514
+ ttp_ingestor.py - GitHub sync + activation (extguard-ttp-sync)
515
+ webhook_server.py - GitHub push receiver (extguard-webhook)
516
+ sigma_generator.py - SIEM export (extguard-sigma)
517
+
518
+ # Supporting modules
519
+ models.py - Shared dataclasses + extension-ID validation
520
+ crx_parser.py - CRX2/CRX3/ZIP/manifest.json parser + limits
521
+ permission_scorer.py - Local permission risk scoring
522
+ publisher_checker.py - CRX3 signed ID + Web Store build check
523
+ osv_lookup.py - VirusTotal hash, npm/OSV check, CDN refs
524
+ virustotal_lookup.py - VirusTotal v3 multi-engine consensus
525
+ update_velocity.py - Semver jump / rollback detection
526
+ code_diff.py - Stage 1f code profile + diff vs baseline
527
+ claude_triage.py - Claude API integration (cache + tool_use)
528
+ ttp_loader.py - Disk-backed TTP library with mtime cache
529
+ config_schema.py - extguard.conf.json validator
530
+ logging_setup.py - Logger + SecretRedactionFilter
531
+
532
+ adapters/ - Sentinel, Splunk, PagerDuty, Slack, retry
533
+ remediators/ - chrome_killer, forensics, cred_rotation
534
+ ttp_library/ - Baseline TTP intel (synced copy: EXTGUARD_HOME)
535
+ dashboard_templates/ - Jinja2 templates for the Flask UI
536
+ dashboard_static/ - CSS for the Flask UI
537
+
538
+ tests/ - 829 pytest tests
539
+ benchmarks/ - 13 pytest-benchmark performance baselines
540
+ fixtures/recorded/ - Real API responses the tests replay
541
+ test_live_apis.py - Opt-in contract tests against real APIs
542
+ test_live_monitor.py - Opt-in monitor test on a headless Chrome
543
+ test_fixtures/ - Simulated malicious + benign manifests
544
+
545
+ extguard.conf.json - Configuration template
546
+ pyproject.toml - Package + ruff config (pytest: pytest.ini)
547
+ Dockerfile - Multi-stage container image
548
+ docker-compose.yml - SOC deployment (dashboard, dispatcher, sync, webhook)
549
+ ```
550
+
551
+ ---
552
+
553
+ ## Development
554
+
555
+ ```powershell
556
+ # Editable install with dev tools (pytest, ruff, build, pytest-benchmark)
557
+ pip install -e ".[dev]"
558
+
559
+ # Run the full test suite (~10 seconds, excludes benchmarks)
560
+ pytest
561
+
562
+ # Run the performance benchmark suite
563
+ pytest tests/benchmarks/
564
+
565
+ # Snapshot a baseline, then compare after changes
566
+ pytest tests/benchmarks/ --benchmark-save=baseline
567
+ pytest tests/benchmarks/ --benchmark-compare=baseline
568
+
569
+ # Contract tests against the REAL Web Store / OSV APIs, plus the runtime
570
+ # monitor on a headless Chrome with a throwaway profile (opt-in, needs
571
+ # network and Chrome)
572
+ $env:EXTGUARD_LIVE_TESTS = "1"; pytest -m live
573
+
574
+ # Lint + format check
575
+ ruff check .
576
+ ruff format --check .
577
+
578
+ # Build a distributable wheel
579
+ python -m build
580
+ ```
581
+
582
+ The CI workflow (`.github/workflows/ci.yml`) runs pytest across Python
583
+ 3.10/3.11/3.12/3.13 on Ubuntu plus ruff + wheel build on every push.
584
+ `.github/workflows/live-api.yml` runs the live contract tests weekly, so a
585
+ change in an upstream API shows up as a failing job instead of a check that
586
+ quietly stops working.
587
+
588
+ ### Adding a new detection rule
589
+
590
+ The pre-install scoring lives in `extguard/permission_scorer.py`. Add an entry to
591
+ `PERMISSION_WEIGHTS`, `COMBO_BONUSES`, or write a new check function, then
592
+ add a test in `tests/test_permission_scorer.py`. The scoring is
593
+ deliberately calibrated so a "TeamPCP-shape" manifest hits 100/100 — keep
594
+ that test green when tuning weights.
595
+
596
+ The runtime detection rules live in `extguard/behavioral_monitor.py`. Each rule is
597
+ a branch in one of the `_handle_*` functions (network requests, console
598
+ calls, dynamic scripts, API hook events). Update the `_rule_to_mitre`
599
+ mapping when adding new rules, and add a matching entry to
600
+ `sigma_generator.SENSOR_RULES` - a test fails until you do.
601
+
602
+ ### Adding a new alert destination
603
+
604
+ Drop a new module in `extguard/adapters/`. The contract is a single
605
+ `send(alert: dict, cfg: dict) -> dict` function that returns
606
+ `{"ok": bool, "error": str?}`. Register it in `alert_dispatcher.ADAPTERS`
607
+ and add the credential schema to `config_schema.ADAPTER_REQUIRED_KEYS`.
608
+ Wire HTTP through `adapters.http_retry.post_with_retry` to get the
609
+ retry/backoff for free.
610
+
611
+ ---
612
+
613
+ ## Workspace setup (optional)
614
+
615
+ For organisations running cloud-managed Chrome via Google Workspace, the
616
+ remediation step can push the extension blocklist policy to every browser
617
+ in an org unit instead of relying on per-host registry writes. One-time
618
+ setup:
619
+
620
+ 1. **Enable the Chrome Policy API** in your Workspace admin console
621
+ (Account > Account settings > Legal and compliance — confirm the API is
622
+ on for your domain).
623
+ 2. **Create a service account** in Google Cloud IAM under the project
624
+ tied to your Workspace.
625
+ 3. **Grant the scopes** `https://www.googleapis.com/auth/chrome.management.policy`
626
+ and `https://www.googleapis.com/auth/admin.directory.orgunit.readonly` in
627
+ Workspace Admin > Security > API controls > Domain-wide delegation.
628
+ 4. **Download the service account JSON key** and set its path in
629
+ `extguard.conf.json` under `workspace.service_account_json`. Keep the
630
+ key out of git (`service-account*.json` is in `.gitignore`).
631
+ 5. **Find your customer ID** at admin.google.com > Account settings >
632
+ Profile and put it in `workspace.customer_id`. Set `workspace.admin_email`
633
+ to an admin the service account acts as (Google requires one).
634
+ 6. Install the optional Google libraries:
635
+
636
+ ```powershell
637
+ pip install -e ".[workspace]"
638
+ ```
639
+
640
+ Then `extguard-remediate ... --workspace-ou /Engineering` blocks the
641
+ extension (policy `chrome.users.apps.InstallType` = BLOCKED) for that org
642
+ unit, in addition to the local machine. Use `--dry-run` to see the exact
643
+ request first. The request follows Google's published format but has not
644
+ been exercised against a live tenant from this project.
645
+
646
+ ---
647
+
648
+ ## TTP library sync
649
+
650
+ The Stage 2 Claude triage reads its threat intelligence from
651
+ `ttp_library/`, and that text goes into Claude's system prompt. Whoever can
652
+ push to the TTP repo can therefore influence every verdict, so updates are
653
+ **staged**:
654
+
655
+ ```powershell
656
+ extguard-ttp-sync --owner myorg --repo extguard-ttp # download to ttp_library.pending/
657
+ extguard-ttp-sync --status # added / changed / removed files
658
+ extguard-ttp-sync --activate # make it live (old copy kept as .previous)
659
+ ```
660
+
661
+ To sync on every push, run the webhook receiver. It is a separate process
662
+ because it has to be reachable from GitHub, and the dashboard must not be:
663
+
664
+ ```powershell
665
+ extguard-webhook --port 8765 # serve it through a TLS reverse proxy or tunnel
666
+ ```
667
+
668
+ Point a GitHub push webhook at `https://<host>/webhook/github` with content
669
+ type `application/json` and the secret from `extguard.conf.json#webhook.secret`.
670
+ The receiver checks the `X-Hub-Signature-256` HMAC (401 if wrong) and
671
+ rejects replayed `X-GitHub-Delivery` IDs (409). It returns 202 at once and
672
+ syncs in the background, one sync at a time.
673
+
674
+ Safety options in the `webhook` section: `auto_activate` (skip the review
675
+ step - only for a repo you fully trust), `require_verified_commit` (refuse
676
+ unless the branch head commit is signature-verified), `delete_orphans`
677
+ (refuses to delete more than half the library or to run after errors). The
678
+ GitHub token is only ever sent to GitHub hosts, files are size-limited, and
679
+ the library is framed as reference data in Claude's prompt.
680
+
681
+ ---
682
+
683
+ ## Sigma rule export
684
+
685
+ ```powershell
686
+ extguard-sigma --output sigma/
687
+ ```
688
+
689
+ Two kinds of rule, depending on which logs you actually have:
690
+
691
+ - **Proxy rule** (`proxy-exfil-hosting.yml`, `category: proxy`) - POSTs to
692
+ exfil-style hosting and Discord / Telegram webhooks in ordinary web-proxy
693
+ logs. Proxy logs can't tell an extension from a tab, so it is a MEDIUM
694
+ hunting rule.
695
+ - **Sensor rules** (`rule-01.yml` ... `rule-07.yml`, `product: extensionguard`)
696
+ - one per monitor rule, over the alerts `extguard-dispatch` forwards to
697
+ Splunk (`sourcetype=extguard:alert`) or Sentinel (`ExtensionGuard_CL`).
698
+ Storage, management-API and cookie activity happen inside the browser;
699
+ no generic log source shows them.
700
+
701
+ The generated README has import instructions for Splunk, Sentinel and
702
+ Elastic. Rule IDs and dates are fixed, so regenerating produces identical
703
+ files unless a rule changed.
704
+
705
+ ---
706
+
707
+ ## Performance
708
+
709
+ Measured baselines for every hot path are in [PERFORMANCE.md](PERFORMANCE.md).
710
+ A single dispatcher handles **20,000+ alerts per second** at the Python
711
+ layer before adapter HTTP calls become the bottleneck. A full
712
+ incident-response sequence (preserve → kill → playbook → PagerDuty)
713
+ completes in under **100 ms of local CPU** plus the PagerDuty HTTP call.
714
+
715
+ Re-measure after any change to a hot path:
716
+
717
+ ```powershell
718
+ pytest tests/benchmarks/ --benchmark-compare=baseline
719
+ ```
720
+
721
+ ---
722
+
723
+ ## License
724
+
725
+ [MIT](LICENSE).
726
+
727
+ ## Acknowledgments
728
+
729
+ - The MITRE ATT&CK framework's Browser Extensions (T1176) technique and
730
+ supporting research.
731
+ - The OSV.dev open-source vulnerability database.
732
+ - VirusTotal for the multi-engine consensus API.
733
+ - The [SigmaHQ](https://github.com/SigmaHQ/sigma) project for the generic
734
+ SIEM detection format.
735
+ - Microsoft Sentinel, Splunk, PagerDuty, and Slack for documenting their
736
+ ingestion APIs well enough that the adapters are ~150 lines each.