feat: initial MV3 detection markers

Nine benign Chrome MV3 detection markers for validating browser extension
security scanners. Each marker exercises one suspicious-behavior signature
without performing real data capture or exfiltration.

Educational and defensive-testing use only. See LICENSE.
This commit is contained in:
tiennm99 committed 2026-05-16 07:54:43 +07:00
commit dc106f9b93
24 files changed
+413

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
.DS_Store
Thumbs.db
*.log
*.swp
*.tmp
.env
.env.*
node_modules/
.idea/
.vscode/
*.crx
*.pem
+47
View File
@@ -0,0 +1,47 @@
MIT License
Copyright (c) 2026 <author>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
------------------------------------------------------------------------------
EDUCATIONAL-USE NOTICE
------------------------------------------------------------------------------
This Software is provided for EDUCATIONAL and DEFENSIVE SECURITY TESTING
purposes only. The benign detection markers contained herein are designed to
validate browser-extension security scanners and contain no working
credential-theft, exfiltration, or surveillance logic.
By using the Software you agree:
1. You will only load these markers in browser profiles, devices, and networks
that you own, or for which you have explicit written authorization to test.
2. You will not use the Software to harass, deceive, defraud, surveil, or
cause harm to any person or system.
3. You are solely responsible for ensuring your use of the Software complies
with all applicable laws, regulations, employer policies, and the terms of
service of any browser, platform, or service involved.
4. You assume all risk associated with the Software, including but not limited
to data loss, account suspension, EDR/AV quarantine actions, and any other
adverse consequence.
------------------------------------------------------------------------------
DISCLAIMER
------------------------------------------------------------------------------
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+111
View File
@@ -0,0 +1,111 @@
# MV3 Detection Markers
> ## ⚠️ Educational Use Only — Use at Your Own Risk
>
> This project provides **benign** Chrome MV3 extensions ("detection markers") for validating browser-extension security scanners. It is the EICAR-equivalent pattern for browser-extension detection.
>
> - **Educational and defensive-testing purpose only.** Do not use for any unlawful activity.
> - **No working credential-theft logic.** Markers exercise suspicious *manifest patterns* and *API call shapes*. They do **not** read or transmit real keystrokes, password values, cookie values, or clipboard contents.
> - **Only load on systems and in browser profiles you own or have written authorization to test.** Do not deploy against third parties.
> - **You assume all risk.** The authors and contributors accept no liability for misuse, data loss, or any consequence of running this software. See `LICENSE`.
>
> If you cannot agree to the above, do not use this project.
---
Benign Chrome MV3 extensions that trigger specific suspicious-behavior signatures. Used as test cases for enterprise extension scanners.
**No real user data is captured or exfiltrated.** Each marker exercises one detection category and logs a literal marker string (and optionally POSTs to a local test endpoint).
---
## Test endpoint setup (optional)
Markers 02 and 03 POST to `http://127.0.0.1:8080/marker`. To capture those POSTs, run any local logger before loading:
```bash
# Quick netcat logger (echoes raw request)
while true; do nc -l -p 8080 -q 1; done
# Or a small Python catcher
python3 -c "
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_POST(self):
n = int(self.headers.get('content-length', 0))
print(self.rfile.read(n).decode(errors='replace'))
self.send_response(204); self.end_headers()
HTTPServer(('127.0.0.1', 8080), H).serve_forever()
"
```
If you skip this, the `fetch` calls silently fail and markers still log to the extension's service-worker / content-script console.
---
## Load procedure
1. Open `chrome://extensions` (Chrome MV3) or `brave://extensions` (Brave).
2. Toggle **Developer mode** (top right).
3. **Load unpacked** → pick one marker folder.
4. Note the generated extension ID.
5. Run your scanner against:
- the folder (static analysis), and
- the loaded extension ID (dynamic / runtime analysis).
6. Record verdict per marker.
For marker 09 (manifest-delta), load `v1-narrow` first, run scanner, then remove and load `v2-wide`. Scanner should flag the permission expansion as a delta-based signal.
---
## Marker matrix
| # | Folder | Detection signal | Real-world example |
|---|---|---|---|
| 01 | `01-form-hook` | Content script attaches `keydown` listener to `input[type=password]` on `<all_urls>` | Credential keylogger pattern |
| 02 | `02-cookie-read` | Service worker calls `chrome.cookies.getAll({})` on install | Session theft pattern (Cyberhaven cluster) |
| 03 | `03-beacon-c2` | `chrome.alarms` periodic POST to external host | C2 beaconing |
| 04 | `04-dnr-redirect` | `declarativeNetRequest` static redirect rule | MV3-only attack vector (replaces MV2 webRequest blocking) |
| 05 | `05-broad-perms` | `<all_urls>` host perm + cookies + scripting + webRequest + tabs combo | Manifest-only signal — pure static test |
| 06 | `06-programmatic-inject` | `chrome.scripting.executeScript` on every tab `complete` event | Stealth content-script injection without `content_scripts` declaration |
| 07 | `07-clipboard-read` | `navigator.clipboard.readText()` on Ctrl+V | Clipboard-stealer pattern |
| 08 | `08-tab-capture` | `chrome.tabs.captureVisibleTab` on action click | Screen-grabber pattern |
| 09 | `09-manifest-delta` | v1 narrow scope → v2 widens to `<all_urls>` + cookies + scripting | Update-poisoning pattern (Cyberhaven technique) |
---
## What these markers do NOT do
- Do not read keystroke values, password contents, cookie values, or clipboard text.
- Do not transmit any data to non-local destinations.
- Do not persist anything to disk or `chrome.storage`.
- Do not modify form behavior, page DOM, or user actions.
Safe to load in a disposable Brave/Chrome dev profile. A clean VM is still recommended so any false-positive enterprise EDR action doesn't disrupt your daily browser.
---
## Per-category recall scoring
For each marker, record:
| Marker | Scanner verdict | Detection rule fired | Notes |
|---|---|---|---|
| 01 | flagged / clean | | |
| 02 | | | |
| ... | | | |
Anything in the **clean** column → a gap. Cross-reference to the prior detection-category report (`research-260516-0713-malicious-chrome-extension-samples-for-detection-testing.md`, §2) to map the miss to a missing rule type.
---
## Removing markers
Each is its own extension — remove via `chrome://extensions` → trash icon. No system changes, no registry keys, no leftover files.
---
## Unresolved
1. **Manifest delta `key` field.** For scanners that require both versions to share an extension ID (i.e., compare by ID), you must inject a stable `key` field into both `manifest.json` files. Generate one with `chrome` packing the v1 folder once and copying the resulting public key. Skipped here because most static scanners diff by name/folder; flag this if your scanner needs ID-stability.
2. **MV2 counterparts** for any of the above markers can be added when you're ready — say the word and I'll generate them alongside.
+23
View File
@@ -0,0 +1,23 @@
// MARKER 01 - Form Hook
// Detection target: keystroke listener attached to password inputs.
// SAFETY: does NOT read event.key or input value. Logs marker string only.
const MARKER = "MARKER_01_FORM_HOOK";
function attachListeners() {
const fields = document.querySelectorAll("input[type=password]");
if (fields.length === 0) return;
fields.forEach((field) => {
if (field.dataset.markerAttached) return;
field.dataset.markerAttached = "1";
field.addEventListener("keydown", () => console.log(`${MARKER} keydown`));
field.addEventListener("input", () => console.log(`${MARKER} input`));
});
console.log(`${MARKER} attached to ${fields.length} password field(s)`);
}
attachListeners();
new MutationObserver(attachListeners).observe(document.documentElement, {
childList: true,
subtree: true,
});
+13
View File
@@ -0,0 +1,13 @@
{
"manifest_version": 3,
"name": "Marker 01 - Form Hook",
"version": "1.0.0",
"description": "Detection marker: content script attaches keystroke listener to password fields. No data captured.",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}
]
}
+9
View File
@@ -0,0 +1,9 @@
{
"manifest_version": 3,
"name": "Marker 02 - Cookie Read",
"version": "1.0.0",
"description": "Detection marker: enumerates cookies via chrome.cookies API. No cookie values transmitted.",
"permissions": ["cookies"],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "service-worker.js" }
}
+17
View File
@@ -0,0 +1,17 @@
// MARKER 02 - Cookie Read
// Detection target: chrome.cookies.getAll() enumeration from service worker.
// SAFETY: counts cookies, logs count, POSTs count-only to local test endpoint.
// Cookie values are NOT transmitted or persisted.
const MARKER = "MARKER_02_COOKIE_READ";
chrome.runtime.onInstalled.addListener(() => {
chrome.cookies.getAll({}, (cookies) => {
const count = cookies.length;
console.log(`${MARKER} enumerated ${count} cookies (values discarded)`);
fetch("http://127.0.0.1:8080/marker", {
method: "POST",
body: `${MARKER}_COUNT=${count}`,
}).catch(() => {});
});
});
+9
View File
@@ -0,0 +1,9 @@
{
"manifest_version": 3,
"name": "Marker 03 - Beacon C2",
"version": "1.0.0",
"description": "Detection marker: periodic beacon via chrome.alarms to local test endpoint.",
"permissions": ["alarms"],
"host_permissions": ["http://127.0.0.1/*"],
"background": { "service_worker": "service-worker.js" }
}
+19
View File
@@ -0,0 +1,19 @@
// MARKER 03 - Beacon C2
// Detection target: service worker schedules periodic external POST via chrome.alarms.
// SAFETY: body is the literal marker string. No user/system data is read or sent.
const MARKER = "MARKER_03_BEACON_C2";
chrome.runtime.onInstalled.addListener(() => {
chrome.alarms.create("marker-beacon", { periodInMinutes: 1 });
console.log(`${MARKER} alarm scheduled`);
});
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name !== "marker-beacon") return;
console.log(`${MARKER} beacon fired`);
fetch("http://127.0.0.1:8080/marker", {
method: "POST",
body: MARKER,
}).catch(() => {});
});
+17
View File
@@ -0,0 +1,17 @@
{
"manifest_version": 3,
"name": "Marker 04 - DNR Redirect",
"version": "1.0.0",
"description": "Detection marker: declarativeNetRequest redirect rule for an invalid TLD.",
"permissions": ["declarativeNetRequest"],
"host_permissions": ["*://marker-test.invalid/*"],
"declarative_net_request": {
"rule_resources": [
{
"id": "marker-rules",
"enabled": true,
"path": "rules.json"
}
]
}
}
+14
View File
@@ -0,0 +1,14 @@
[
{
"id": 1,
"priority": 1,
"action": {
"type": "redirect",
"redirect": { "url": "https://example.com/marker-04-dnr-redirect" }
},
"condition": {
"urlFilter": "marker-test.invalid",
"resourceTypes": ["main_frame"]
}
}
]
+17
View File
@@ -0,0 +1,17 @@
{
"manifest_version": 3,
"name": "Marker 05 - Broad Permissions",
"version": "1.0.0",
"description": "Detection marker: oversized permission combo. No behavior beyond a console log.",
"permissions": [
"cookies",
"scripting",
"webRequest",
"storage",
"tabs",
"alarms",
"clipboardRead"
],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "service-worker.js" }
}
+5
View File
@@ -0,0 +1,5 @@
// MARKER 05 - Broad Permissions
// Detection target: manifest declares oversized permission combo.
// Behavior: none. Pure static-analysis test.
console.log("MARKER_05_BROAD_PERMS loaded");
@@ -0,0 +1,2 @@
// MARKER 06 injected payload. Logs marker string only.
console.log("MARKER_06_INJECTED_PAYLOAD");
@@ -0,0 +1,9 @@
{
"manifest_version": 3,
"name": "Marker 06 - Programmatic Inject",
"version": "1.0.0",
"description": "Detection marker: chrome.scripting.executeScript on every tab complete event.",
"permissions": ["scripting", "tabs"],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "service-worker.js" }
}
@@ -0,0 +1,14 @@
// MARKER 06 - Programmatic Inject
// Detection target: chrome.scripting.executeScript injection on tab events,
// bypassing the static content_scripts declaration.
const MARKER = "MARKER_06_PROGRAMMATIC_INJECT";
chrome.tabs.onUpdated.addListener((tabId, info, tab) => {
if (info.status !== "complete") return;
if (!tab.url || !tab.url.startsWith("http")) return;
chrome.scripting
.executeScript({ target: { tabId }, files: ["injected.js"] })
.then(() => console.log(`${MARKER} injected into tab ${tabId}`))
.catch(() => {});
});
+15
View File
@@ -0,0 +1,15 @@
// MARKER 07 - Clipboard Read
// Detection target: navigator.clipboard.readText() triggered by user input.
// SAFETY: discards clipboard content; logs length only.
const MARKER = "MARKER_07_CLIPBOARD_READ";
document.addEventListener("keydown", async (e) => {
if (!(e.ctrlKey && e.key === "v")) return;
try {
const text = await navigator.clipboard.readText();
console.log(`${MARKER} length=${text.length} (content discarded)`);
} catch {
/* permission may not be granted yet */
}
});
+14
View File
@@ -0,0 +1,14 @@
{
"manifest_version": 3,
"name": "Marker 07 - Clipboard Read",
"version": "1.0.0",
"description": "Detection marker: reads clipboard on Ctrl+V. Content discarded.",
"permissions": ["clipboardRead"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}
]
}
+10
View File
@@ -0,0 +1,10 @@
{
"manifest_version": 3,
"name": "Marker 08 - Tab Capture",
"version": "1.0.0",
"description": "Detection marker: chrome.tabs.captureVisibleTab on action click. Capture discarded.",
"permissions": ["activeTab"],
"host_permissions": ["<all_urls>"],
"action": { "default_title": "Marker 08 - Click to test capture" },
"background": { "service_worker": "service-worker.js" }
}
+12
View File
@@ -0,0 +1,12 @@
// MARKER 08 - Tab Capture
// Detection target: chrome.tabs.captureVisibleTab.
// SAFETY: capture data URL is discarded immediately, never transmitted.
const MARKER = "MARKER_08_TAB_CAPTURE";
chrome.action.onClicked.addListener(() => {
chrome.tabs.captureVisibleTab({ format: "png" }, (dataUrl) => {
if (!dataUrl) return;
console.log(`${MARKER} captured tab, size=${dataUrl.length} (discarded)`);
});
});
@@ -0,0 +1,9 @@
{
"manifest_version": 3,
"name": "Marker 09 - Manifest Delta",
"version": "1.0.0",
"description": "Detection marker v1: narrow scope (example.com only, storage only).",
"permissions": ["storage"],
"host_permissions": ["https://example.com/*"],
"background": { "service_worker": "service-worker.js" }
}
@@ -0,0 +1,3 @@
// MARKER 09 v1 - Narrow Scope
// Baseline manifest. Load first; run scanner; verdict should be clean / low risk.
console.log("MARKER_09_V1_NARROW loaded");
@@ -0,0 +1,9 @@
{
"manifest_version": 3,
"name": "Marker 09 - Manifest Delta",
"version": "2.0.0",
"description": "Detection marker v2: widened scope (simulates post-update compromise).",
"permissions": ["storage", "cookies", "scripting", "webRequest", "tabs"],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "service-worker.js" }
}
@@ -0,0 +1,3 @@
// MARKER 09 v2 - Wide Scope
// Compromise-update manifest. Load after v1; scanner should flag the permission delta.
console.log("MARKER_09_V2_WIDE loaded");