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.
- extensionguard-0.3.0.dist-info/METADATA +736 -0
- extensionguard-0.3.0.dist-info/RECORD +50 -0
- extensionguard-0.3.0.dist-info/WHEEL +5 -0
- extensionguard-0.3.0.dist-info/entry_points.txt +9 -0
- extensionguard-0.3.0.dist-info/licenses/LICENSE +21 -0
- extensionguard-0.3.0.dist-info/top_level.txt +1 -0
- extguard/__init__.py +19 -0
- extguard/__main__.py +6 -0
- extguard/adapters/__init__.py +0 -0
- extguard/adapters/http_retry.py +117 -0
- extguard/adapters/pagerduty.py +197 -0
- extguard/adapters/sentinel.py +191 -0
- extguard/adapters/slack.py +223 -0
- extguard/adapters/splunk.py +101 -0
- extguard/alert_dispatcher.py +706 -0
- extguard/behavioral_monitor.py +1427 -0
- extguard/claude_triage.py +366 -0
- extguard/code_diff.py +375 -0
- extguard/config_schema.py +234 -0
- extguard/crx_parser.py +399 -0
- extguard/dashboard.py +530 -0
- extguard/dashboard_static/style.css +146 -0
- extguard/dashboard_templates/base.html +29 -0
- extguard/dashboard_templates/case.html +126 -0
- extguard/dashboard_templates/error.html +9 -0
- extguard/dashboard_templates/index.html +86 -0
- extguard/dashboard_templates/verify_result.html +43 -0
- extguard/logging_setup.py +237 -0
- extguard/main.py +811 -0
- extguard/models.py +85 -0
- extguard/osv_lookup.py +331 -0
- extguard/paths.py +102 -0
- extguard/permission_scorer.py +423 -0
- extguard/publisher_checker.py +499 -0
- extguard/remediation.py +726 -0
- extguard/remediators/__init__.py +0 -0
- extguard/remediators/chrome_killer.py +596 -0
- extguard/remediators/cred_rotation.py +578 -0
- extguard/remediators/forensics.py +468 -0
- extguard/sigma_generator.py +450 -0
- extguard/ttp_ingestor.py +647 -0
- extguard/ttp_library/README.md +36 -0
- extguard/ttp_library/campaigns/shai_hulud.md +39 -0
- extguard/ttp_library/campaigns/teamccp_nx_console.md +33 -0
- extguard/ttp_library/mitre_reference.md +18 -0
- extguard/ttp_library/patterns/credential_chains.md +45 -0
- extguard/ttp_loader.py +173 -0
- extguard/update_velocity.py +310 -0
- extguard/virustotal_lookup.py +231 -0
- 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.
|