mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-30 19:16:07 +00:00
Compare commits
3 Commits
e7a84f0380
...
b8da257b83
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8da257b83 | ||
| b2ee2a97ef | |||
| c77e6250a9 |
|
|
@ -3,6 +3,23 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-06-13 — Browser extension : emancipate cartographie live (ref #532)
|
||||
|
||||
Nouveau client `clients/webext-toolbox/` (MV3 Firefox `.xpi` + Chromium),
|
||||
sœur de l'app Android. Surface la cartographie sociale R3 dans le
|
||||
navigateur : badge live des traceurs + popup (4 tuiles + mini Round-Eye
|
||||
graph SVG sans dépendance + top-traceurs taggés CDN/anti-bot/opérateur +
|
||||
actions cartographie/PDF/RGPD-wipe). Parle uniquement à la cabine via R3
|
||||
(pas de CORS backend grâce à host_permissions).
|
||||
|
||||
`secubox-toolbox 2.6.14` : `GET /wg/toolbox.xpi` (local sinon 302 →
|
||||
release), bouton onboard, helper `secubox-toolbox-fetch-xpi`, postinst
|
||||
dir. CI `build-webext.yml` (`web-ext lint` + build, release asset sur tag
|
||||
`webext-v*`). Suivi : signature AMO, SSE `/social/live`, icône PNG
|
||||
Chromium, Poke/Emancipate (#525).
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-13 — Android ToolBox app : serve + root-mode silent onboarding (ref #531/#536/#538)
|
||||
|
||||
App compagnon Android one-tap R3 (`clients/android-toolbox/`, Kotlin + Compose).
|
||||
|
|
|
|||
|
|
@ -5,17 +5,22 @@
|
|||
|
||||
## 🔥 P0 — Immediate (in flight)
|
||||
|
||||
### Android ToolBox client (`clients/android-toolbox/`)
|
||||
### ToolBox clients (`clients/`)
|
||||
|
||||
- [x] **#531 scaffold + CI** — Gradle/Compose one-tap onboarding, debug APK
|
||||
via `build-android-apk.yml`. CI green.
|
||||
- [x] **#536 serve from toolbox** — `GET /wg/toolbox.apk` + onboard button +
|
||||
- [x] **#531 Android scaffold + CI** — Gradle/Compose one-tap onboarding,
|
||||
debug APK via `build-android-apk.yml`. CI green.
|
||||
- [x] **#536 serve APK from toolbox** — `GET /wg/toolbox.apk` + onboard button +
|
||||
`secubox-toolbox-fetch-apk` helper.
|
||||
- [x] **#538 root-mode silent** (PR #539) — system CA install + native kernel
|
||||
WireGuard + auto R3 verify, gated behind explicit root tap.
|
||||
- [ ] **release signing** — keystore secret in CI for a stable published
|
||||
fingerprint (currently debug-signed sideload).
|
||||
- [ ] **#532 browser XPI** — Firefox/Chrome extension equivalent (later).
|
||||
- [x] **#538 Android root-mode silent** (PR #539) — system CA install + native
|
||||
kernel WireGuard + auto R3 verify, gated behind explicit root tap.
|
||||
- [x] **#532 browser extension** (`clients/webext-toolbox/`) — MV3 Firefox
|
||||
`.xpi`/Chromium; live tracker badge + popup mini Round-Eye graph over
|
||||
`/social/*`; `GET /wg/toolbox.xpi` + fetch helper + `build-webext.yml`.
|
||||
- [ ] **release signing** — Android keystore + AMO `.xpi` signing secrets in CI
|
||||
for stable published fingerprints (currently unsigned sideload).
|
||||
- [ ] **#532 follow-ups** — optional `GET /social/live/{token}` SSE (replace the
|
||||
client-side poll) ; Poke/Emancipate per-site control once #525 (deception)
|
||||
ships ; Chromium PNG icon rasterisation for the Web Store.
|
||||
|
||||
### Phase 13 — Protection enforcement plane (#519) — ✅ COMPLETE
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,32 @@
|
|||
|
||||
---
|
||||
|
||||
## 🔄 2026-06-13 : Browser extension — emancipate cartographie live (#532)
|
||||
|
||||
Extension navigateur (`clients/webext-toolbox/`, MV3 Firefox `.xpi` +
|
||||
Chromium) sœur de l'app Android. Sort la *cartographie sociale* R3 dans
|
||||
le navigateur : badge live des traceurs + popup.
|
||||
|
||||
- **Extension** : `manifest.json` (MV3, background `service_worker` +
|
||||
`scripts` pour FF115+/Chromium), `api.js` (client `/wg/r3-check`,
|
||||
`/social/me` → token, `/social/graph/{token}`, `/social/wipe`),
|
||||
`background.js` (badge = total_trackers, re-pair silencieux si token
|
||||
expiré, couleur escalade gold→anti-bot→opérateur), popup (4 tuiles
|
||||
stats + **mini Round-Eye graph SVG sans dépendance** + top-traceurs
|
||||
taggés CDN/anti-bot/opérateur + actions cartographie/PDF/RGPD-wipe),
|
||||
options (hôte/fenêtre/token manuel). Pas de CORS backend nécessaire
|
||||
(host_permissions). Validé : JSON+JS+SVG OK, `.xpi` build 11.8 KB.
|
||||
- **Serve depuis la toolbox** (`2.6.14`) : `GET /wg/toolbox.xpi` (local
|
||||
sinon 302 → release), bouton `🧩 Extension navigateur` sur les 2
|
||||
panneaux onboard, helper `secubox-toolbox-fetch-xpi`, postinst dir.
|
||||
- **CI** : `build-webext.yml` — `web-ext lint` + build, artifact, release
|
||||
asset `secubox-toolbox-webext.xpi` sur tag `webext-v*`.
|
||||
- **Reste à faire** : signature AMO (`.xpi` non signé = sideload/dev) ;
|
||||
endpoint SSE `/social/live/{token}` optionnel ; icône PNG Chromium ;
|
||||
contrôle Poke/Emancipate par-site quand #525 (déception) arrive.
|
||||
|
||||
---
|
||||
|
||||
## 🔄 2026-06-13 : Android ToolBox app — serve + root-mode silent onboarding (#531/#536/#538)
|
||||
|
||||
App compagnon Android **one-tap R3** pour la cabine VILLAGE3B
|
||||
|
|
|
|||
60
.github/workflows/build-webext.yml
vendored
Normal file
60
.github/workflows/build-webext.yml
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Build the SecuBox ToolBoX browser extension (#532).
|
||||
# Plain JS/HTML/CSS — no bundler. web-ext lints + packages the .xpi.
|
||||
# Produces an unsigned .xpi artifact; release signing (AMO) is a
|
||||
# follow-up (needs AMO API credentials as secrets).
|
||||
name: build-webext
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths: [ "clients/webext-toolbox/**", ".github/workflows/build-webext.yml" ]
|
||||
tags: [ "webext-v*" ]
|
||||
pull_request:
|
||||
paths: [ "clients/webext-toolbox/**" ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write # needed to attach the .xpi to a release on tags
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
working-directory: clients/webext-toolbox
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Lint (web-ext)
|
||||
run: npx --yes web-ext lint --source-dir . --self-hosted --ignore-files build.sh README.md
|
||||
|
||||
- name: Build .xpi (web-ext)
|
||||
run: |
|
||||
npx --yes web-ext build --source-dir . \
|
||||
--artifacts-dir web-ext-artifacts --overwrite-dest \
|
||||
--ignore-files build.sh README.md \
|
||||
--filename "secubox-toolbox-webext.xpi"
|
||||
|
||||
- name: Upload .xpi artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: secubox-toolbox-webext
|
||||
path: clients/webext-toolbox/web-ext-artifacts/secubox-toolbox-webext.xpi
|
||||
if-no-files-found: error
|
||||
|
||||
# On webext-v* tags, publish the .xpi as a release asset under the
|
||||
# stable name the toolbox fetch helper + /wg/toolbox.xpi expect.
|
||||
# `latest/download/secubox-toolbox-webext.xpi` resolves to the
|
||||
# newest release.
|
||||
- name: Publish release
|
||||
if: startsWith(github.ref, 'refs/tags/webext-v')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: clients/webext-toolbox/web-ext-artifacts/secubox-toolbox-webext.xpi
|
||||
fail_on_unmatched_files: true
|
||||
5
clients/webext-toolbox/.gitignore
vendored
Normal file
5
clients/webext-toolbox/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# build artefacts
|
||||
*.xpi
|
||||
*.zip
|
||||
*.crx
|
||||
web-ext-artifacts/
|
||||
93
clients/webext-toolbox/README.md
Normal file
93
clients/webext-toolbox/README.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
# SecuBox ToolBoX — browser extension (Cartographie sociale, #532)
|
||||
|
||||
A WebExtension (Firefox `.xpi` + Chromium MV3) that **emancipates** the R3
|
||||
toolbox live tracker analysis into the browser: instead of only seeing the
|
||||
*cartographie sociale* on `kbin/social/me`, a toolbar badge ticks up as
|
||||
trackers fire, and a popup shows who is watching you — live.
|
||||
|
||||
Sibling of [`clients/android-toolbox/`](../android-toolbox/). Talks **only**
|
||||
to your cabine over the R3 tunnel — no third-party calls.
|
||||
|
||||
## What it does
|
||||
|
||||
- **Pairing** — calls `/social/me` over the tunnel, which 303-redirects to
|
||||
`/social/{token}`; the extension reads the minted HMAC token from the
|
||||
final URL. Anonymous (rotating `mac_hash`), no account. Manual token entry
|
||||
available in the options page.
|
||||
- **Live badge** — the toolbar icon shows the live tracker count for the
|
||||
session (polled once a minute). Colour escalates: gold → 🟥 anti-bot
|
||||
present → 🟪 operator-grade present.
|
||||
- **Popup** — four stat tiles (trackers / sites / anti-bot / operator-grade),
|
||||
a dependency-free **mini Round-Eye graph** (device centre, trackers on the
|
||||
ring, radius by hits, colour by tier), and a top-tracker list with CDN
|
||||
(12.A) / anti-bot (12.B) / operator-grade (12.C) tags.
|
||||
- **Actions** — *Cartographie complète* (opens the full d3 view at
|
||||
`/social/{token}`), *Rapport PDF* (`/social/report/{token}.pdf`), and
|
||||
*Effacer mes données* (RGPD art. 17 wipe → `POST /social/wipe/{token}`).
|
||||
|
||||
## Install
|
||||
|
||||
The toolbox serves the built extension:
|
||||
|
||||
```
|
||||
https://kbin.<board>.secubox.in/wg/toolbox.xpi
|
||||
```
|
||||
|
||||
The kbin onboard panel exposes a **🧩 Extension navigateur (cartographie)**
|
||||
button. When a local build is present the cabine serves it; otherwise it
|
||||
302-redirects to the latest GitHub release asset `secubox-toolbox-webext.xpi`.
|
||||
|
||||
- **Firefox** — open the `.xpi`. A permanent install needs an AMO-signed
|
||||
build (release CI step / `web-ext sign`); for development use
|
||||
*about:debugging → Load Temporary Add-on*, or an ESR/Dev build with
|
||||
`xpinstall.signatures.required=false`.
|
||||
- **Chromium** — load unpacked (`chrome://extensions` → Developer mode).
|
||||
Note: Chromium action icons must be raster — rasterise `icons/icon.svg`
|
||||
to PNG before a Chrome Web Store build (Firefox accepts the SVG as-is).
|
||||
|
||||
## Build
|
||||
|
||||
No bundler — the extension is plain JS/HTML/CSS. CI zips it:
|
||||
|
||||
- GitHub Actions `build-webext.yml` → `.xpi` artifact on push to `master` /
|
||||
PRs touching `clients/webext-toolbox/**`; tagging `webext-v*` publishes the
|
||||
`.xpi` as a release asset.
|
||||
|
||||
Locally:
|
||||
|
||||
```bash
|
||||
cd clients/webext-toolbox
|
||||
./build.sh # → secubox-toolbox-webext-<version>.xpi
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `manifest.json` | MV3, cross-browser background (`service_worker` + `scripts`) |
|
||||
| `api.js` | shared client over `/wg/r3-check`, `/social/*` |
|
||||
| `background.js` | badge sync + silent re-pair (SW or event page) |
|
||||
| `popup/` | live view, mini graph (`graph.js`), actions |
|
||||
| `options/` | host / window / manual token |
|
||||
|
||||
## Cabine endpoints consumed
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `/wg/r3-check` | tunnel presence indicator |
|
||||
| `/social/me` | pair → mint token (303 → `/social/{token}`) |
|
||||
| `/social/graph/{token}?since=` | per-session tracker graph JSON |
|
||||
| `/social/wipe/{token}` | RGPD art. 17 erasure |
|
||||
| `/social/{token}` | full d3 cartographie page |
|
||||
| `/social/report/{token}.pdf` | bilingual PDF report |
|
||||
|
||||
## Notes
|
||||
|
||||
- No server-side CORS needed: an MV3 extension with `host_permissions` for
|
||||
`*.secubox.in` fetches cross-origin from its background without CORS.
|
||||
- MVP polls `/social/graph` and computes the delta client-side; a future
|
||||
`GET /social/live/{token}` (SSE) can replace the poll. The deception-plane
|
||||
*Poke/Emancipate* per-site control lands once #525 ships.
|
||||
|
||||
License `LicenseRef-CMSD-1.0`.
|
||||
116
clients/webext-toolbox/api.js
Normal file
116
clients/webext-toolbox/api.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
//
|
||||
// SecuBox-Deb :: webext-toolbox :: api
|
||||
// Thin client over the R3 toolbox social endpoints. Shared by the
|
||||
// background service worker and the popup. Cross-origin fetches are
|
||||
// allowed because the extension holds host_permissions for the cabine
|
||||
// vhosts — no server-side CORS needed.
|
||||
|
||||
// browser (Firefox promise API) || chrome (Chromium / FF MV3 SW)
|
||||
const ext = globalThis.browser || globalThis.chrome;
|
||||
|
||||
const DEFAULTS = {
|
||||
host: "kbin.gk2.secubox.in",
|
||||
token: "",
|
||||
since: 86400,
|
||||
};
|
||||
|
||||
// base URL from a stored host (accept bare host or full origin)
|
||||
function baseUrl(host) {
|
||||
const h = (host || DEFAULTS.host).trim().replace(/\/+$/, "");
|
||||
if (/^https?:\/\//i.test(h)) return h;
|
||||
return `https://${h}`;
|
||||
}
|
||||
|
||||
async function getConfig() {
|
||||
const stored = await ext.storage.local.get(["host", "token", "since"]);
|
||||
return { ...DEFAULTS, ...stored };
|
||||
}
|
||||
|
||||
async function setConfig(patch) {
|
||||
await ext.storage.local.set(patch);
|
||||
return getConfig();
|
||||
}
|
||||
|
||||
// Extract the HMAC token from a /social/{token} URL path.
|
||||
function tokenFromUrl(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
const m = u.pathname.match(/\/social\/([^/?#]+)/);
|
||||
if (m && m[1] !== "me" && m[1].split(".").length === 4) return m[1];
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pair: hit /social/me over the tunnel; it 303-redirects to
|
||||
// /social/{token}. fetch follows the redirect, so response.url carries
|
||||
// the minted token. Returns the token or throws.
|
||||
async function pair(host) {
|
||||
const url = `${baseUrl(host)}/social/me`;
|
||||
const resp = await fetch(url, { redirect: "follow", credentials: "omit" });
|
||||
const tok = tokenFromUrl(resp.url);
|
||||
if (!tok) throw new Error("pairing failed — not on the R3 tunnel?");
|
||||
return tok;
|
||||
}
|
||||
|
||||
// r3-check: is this client on the R3 tunnel right now?
|
||||
async function r3Check(host) {
|
||||
try {
|
||||
const resp = await fetch(`${baseUrl(host)}/wg/r3-check`, { credentials: "omit" });
|
||||
if (!resp.ok) return { tunnel: false, peer_ip: null };
|
||||
return await resp.json();
|
||||
} catch (_) {
|
||||
return { tunnel: false, peer_ip: null };
|
||||
}
|
||||
}
|
||||
|
||||
// graph: the per-session cartographie JSON. Throws on HTTP error so the
|
||||
// caller can show "token expired — re-pair".
|
||||
async function graph(host, token, since) {
|
||||
const qs = new URLSearchParams({ since: String(since || DEFAULTS.since) });
|
||||
const resp = await fetch(`${baseUrl(host)}/social/graph/${token}?${qs}`, {
|
||||
credentials: "omit",
|
||||
});
|
||||
if (resp.status === 403 || resp.status === 404) {
|
||||
throw new Error("token-expired");
|
||||
}
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
// RGPD art.17 wipe.
|
||||
async function wipe(host, token) {
|
||||
const resp = await fetch(`${baseUrl(host)}/social/wipe/${token}`, {
|
||||
method: "POST",
|
||||
credentials: "omit",
|
||||
});
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
return await resp.json();
|
||||
}
|
||||
|
||||
function socialUrl(host, token) {
|
||||
return `${baseUrl(host)}/social/${token}`;
|
||||
}
|
||||
function reportUrl(host, token) {
|
||||
return `${baseUrl(host)}/social/report/${token}.pdf`;
|
||||
}
|
||||
|
||||
const SbxApi = {
|
||||
DEFAULTS,
|
||||
ext,
|
||||
baseUrl,
|
||||
getConfig,
|
||||
setConfig,
|
||||
pair,
|
||||
r3Check,
|
||||
graph,
|
||||
wipe,
|
||||
socialUrl,
|
||||
reportUrl,
|
||||
tokenFromUrl,
|
||||
};
|
||||
|
||||
// Usable both as a classic background script (globalThis) and an ES-less
|
||||
// service worker. No module syntax to stay loadable as plain script.
|
||||
globalThis.SbxApi = SbxApi;
|
||||
78
clients/webext-toolbox/background.js
Normal file
78
clients/webext-toolbox/background.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
//
|
||||
// SecuBox-Deb :: webext-toolbox :: background
|
||||
// Keeps the toolbar badge in sync with the live tracker count and
|
||||
// re-pairs over the R3 tunnel when the token expires. Works as a
|
||||
// Chromium MV3 service worker (importScripts) AND a Firefox event page
|
||||
// (api.js preloaded via background.scripts).
|
||||
|
||||
if (typeof importScripts === "function") {
|
||||
// Chromium service worker: api.js isn't auto-loaded, pull it in.
|
||||
try { importScripts("api.js"); } catch (_) {}
|
||||
}
|
||||
|
||||
const api = globalThis.SbxApi;
|
||||
const ext = globalThis.browser || globalThis.chrome;
|
||||
|
||||
const ALARM = "sbx-refresh";
|
||||
const PERIOD_MIN = 1; // poll the cabine once a minute
|
||||
|
||||
function setBadge(text, color) {
|
||||
try {
|
||||
ext.action.setBadgeText({ text: text || "" });
|
||||
if (color) ext.action.setBadgeBackgroundColor({ color });
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Pull the graph, update the badge with the live tracker count. Auto
|
||||
// re-pairs once if the stored token has expired.
|
||||
async function refresh() {
|
||||
const cfg = await api.getConfig();
|
||||
if (!cfg.host) { setBadge("", "#6b6b7a"); return; }
|
||||
|
||||
let token = cfg.token;
|
||||
const run = async (tok) => api.graph(cfg.host, tok, cfg.since);
|
||||
|
||||
try {
|
||||
if (!token) token = await api.pair(cfg.host);
|
||||
let data;
|
||||
try {
|
||||
data = await run(token);
|
||||
} catch (e) {
|
||||
if (String(e.message) === "token-expired") {
|
||||
token = await api.pair(cfg.host); // one silent re-pair
|
||||
data = await run(token);
|
||||
} else throw e;
|
||||
}
|
||||
await api.setConfig({ token });
|
||||
const n = (data.stats && data.stats.total_trackers) || 0;
|
||||
// colour escalates with operator-grade / anti-bot presence
|
||||
const opg = (data.stats && data.stats.opgrade_sites) || 0;
|
||||
const ab = (data.stats && data.stats.antibot_sites) || 0;
|
||||
const color = opg > 0 ? "#6e40c9" : ab > 0 ? "#e63946" : "#c9a84c";
|
||||
setBadge(n > 999 ? "999+" : String(n), color);
|
||||
await ext.storage.local.set({ lastStats: data.stats || {}, lastError: "" });
|
||||
} catch (e) {
|
||||
setBadge("!", "#6b6b7a");
|
||||
await ext.storage.local.set({ lastError: String(e.message || e) });
|
||||
}
|
||||
}
|
||||
|
||||
ext.runtime.onInstalled.addListener(() => {
|
||||
ext.alarms.create(ALARM, { periodInMinutes: PERIOD_MIN });
|
||||
refresh();
|
||||
});
|
||||
ext.runtime.onStartup && ext.runtime.onStartup.addListener(() => {
|
||||
ext.alarms.create(ALARM, { periodInMinutes: PERIOD_MIN });
|
||||
refresh();
|
||||
});
|
||||
ext.alarms.onAlarm.addListener((a) => { if (a.name === ALARM) refresh(); });
|
||||
|
||||
// popup asks for an immediate refresh after pairing / config change
|
||||
ext.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
||||
if (msg && msg.type === "refresh") {
|
||||
refresh().then(() => sendResponse({ ok: true }));
|
||||
return true; // async response
|
||||
}
|
||||
});
|
||||
24
clients/webext-toolbox/build.sh
Executable file
24
clients/webext-toolbox/build.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
#
|
||||
# SecuBox ToolBoX Cartographie — build the unsigned .xpi (a zip of the
|
||||
# extension dir). Firefox loads it as-is (temporary add-on / ESR with
|
||||
# signatures off) ; a release build signs it via web-ext / AMO.
|
||||
# Usage: ./build.sh → produces ./secubox-toolbox-webext-<version>.xpi
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
VER=$(grep -oE '"version"[^,]*' manifest.json | grep -oE '[0-9.]+' | head -1)
|
||||
OUT="secubox-toolbox-webext-${VER}.xpi"
|
||||
rm -f "$OUT"
|
||||
|
||||
# -FS = sync (drop stale entries) ; exclude VCS, dotfiles, build script,
|
||||
# any previously built artefact, and docs.
|
||||
zip -r -FS "$OUT" . \
|
||||
-x '*.git*' '*/.*' 'build.sh' '*.xpi' 'README.md' >/dev/null
|
||||
|
||||
echo "built $OUT ($(stat -c%s "$OUT" 2>/dev/null || stat -f%z "$OUT") bytes)"
|
||||
echo "Firefox: about:debugging → This Firefox → Load Temporary Add-on → pick the .xpi (or manifest.json)."
|
||||
echo "Permanent install needs signing (web-ext sign / AMO) or Dev/ESR with xpinstall.signatures.required=false."
|
||||
echo "Chromium: action icons must be raster — rasterise icons/icon.svg to PNG before a Chromium store build."
|
||||
22
clients/webext-toolbox/icons/icon.svg
Normal file
22
clients/webext-toolbox/icons/icon.svg
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
|
||||
<rect width="128" height="128" rx="24" fill="#0a0a0f"/>
|
||||
<!-- outer eye almond -->
|
||||
<path d="M64 36 C92 36 114 64 114 64 C114 64 92 92 64 92 C36 92 14 64 14 64 C14 64 36 36 64 36 Z"
|
||||
fill="none" stroke="#c9a84c" stroke-width="5"/>
|
||||
<!-- iris -->
|
||||
<circle cx="64" cy="64" r="20" fill="#0c0c12" stroke="#00ff41" stroke-width="4"/>
|
||||
<circle cx="64" cy="64" r="7" fill="#00ff41"/>
|
||||
<!-- tracker spokes -->
|
||||
<g stroke="#6e40c9" stroke-width="3" opacity="0.8">
|
||||
<line x1="64" y1="64" x2="104" y2="40"/>
|
||||
<line x1="64" y1="64" x2="24" y2="40"/>
|
||||
<line x1="64" y1="64" x2="100" y2="92"/>
|
||||
</g>
|
||||
<g fill="#00d4ff">
|
||||
<circle cx="104" cy="40" r="5"/>
|
||||
<circle cx="24" cy="40" r="5"/>
|
||||
</g>
|
||||
<circle cx="100" cy="92" r="5" fill="#e63946"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 951 B |
36
clients/webext-toolbox/manifest.json
Normal file
36
clients/webext-toolbox/manifest.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "SecuBox ToolBoX — Cartographie sociale",
|
||||
"version": "0.1.0",
|
||||
"description": "Surface the SecuBox R3 toolbox live tracker analysis (cartographie sociale) in your browser: live badge, per-session trackers, mini Round-Eye graph, RGPD wipe + PDF report.",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "secubox-toolbox-webext@cybermind.fr",
|
||||
"strict_min_version": "115.0"
|
||||
}
|
||||
},
|
||||
"permissions": ["storage", "alarms"],
|
||||
"host_permissions": [
|
||||
"*://*.secubox.in/*"
|
||||
],
|
||||
"action": {
|
||||
"default_title": "SecuBox Cartographie",
|
||||
"default_popup": "popup/popup.html",
|
||||
"default_icon": {
|
||||
"48": "icons/icon.svg",
|
||||
"128": "icons/icon.svg"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"48": "icons/icon.svg",
|
||||
"128": "icons/icon.svg"
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "background.js",
|
||||
"scripts": ["api.js", "background.js"]
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options/options.html",
|
||||
"open_in_tab": true
|
||||
}
|
||||
}
|
||||
45
clients/webext-toolbox/options/options.html
Normal file
45
clients/webext-toolbox/options/options.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>SecuBox Cartographie — Réglages</title>
|
||||
<style>
|
||||
body { background:#0a0a0f; color:#e8e6d9; font:14px/1.5 system-ui,sans-serif;
|
||||
max-width:520px; margin:40px auto; padding:0 20px; }
|
||||
h1 { color:#c9a84c; font-size:18px; }
|
||||
label { display:block; color:#6b6b7a; font-size:12px; margin:14px 0 4px; }
|
||||
input,select { width:100%; padding:8px; border-radius:6px; border:1px solid #333;
|
||||
background:#14141c; color:#e8e6d9; }
|
||||
button { margin-top:16px; padding:9px 14px; border-radius:6px; border:1px solid #c9a84c;
|
||||
background:#c9a84c; color:#0a0a0f; font-weight:700; cursor:pointer; }
|
||||
.muted { color:#6b6b7a; font-size:12px; }
|
||||
#msg { color:#00ff41; min-height:18px; margin-top:10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>👁️ SecuBox Cartographie — Réglages</h1>
|
||||
<p class="muted">L'extension parle uniquement à ta cabine via le tunnel R3.
|
||||
Aucune donnée n'est envoyée ailleurs.</p>
|
||||
|
||||
<label>Borne (hôte de la cabine)
|
||||
<input id="host" type="text" placeholder="kbin.gk2.secubox.in" autocomplete="off">
|
||||
</label>
|
||||
<label>Fenêtre d'analyse
|
||||
<select id="since">
|
||||
<option value="3600">1 heure</option>
|
||||
<option value="86400" selected>24 heures</option>
|
||||
<option value="604800">7 jours</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Jeton de session (optionnel — sinon appairage auto via R3)
|
||||
<input id="token" type="text" placeholder="mac.exp.nonce.sig" autocomplete="off">
|
||||
</label>
|
||||
|
||||
<button id="save">Enregistrer</button>
|
||||
<p id="msg"></p>
|
||||
|
||||
<script src="../api.js"></script>
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
24
clients/webext-toolbox/options/options.js
Normal file
24
clients/webext-toolbox/options/options.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
const api = globalThis.SbxApi;
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
async function load() {
|
||||
const cfg = await api.getConfig();
|
||||
$("host").value = cfg.host;
|
||||
$("token").value = cfg.token || "";
|
||||
$("since").value = String(cfg.since);
|
||||
}
|
||||
|
||||
$("save").addEventListener("click", async () => {
|
||||
await api.setConfig({
|
||||
host: $("host").value.trim() || api.DEFAULTS.host,
|
||||
token: $("token").value.trim(),
|
||||
since: parseInt($("since").value, 10) || api.DEFAULTS.since,
|
||||
});
|
||||
api.ext.runtime.sendMessage({ type: "refresh" });
|
||||
$("msg").textContent = "Enregistré ✓";
|
||||
setTimeout(() => ($("msg").textContent = ""), 1500);
|
||||
});
|
||||
|
||||
load();
|
||||
80
clients/webext-toolbox/popup/graph.js
Normal file
80
clients/webext-toolbox/popup/graph.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
//
|
||||
// Dependency-free mini "Round-Eye" cartographie : the device at the
|
||||
// centre, top trackers on an outer ring, radius/colour by hits + tier.
|
||||
// A compact stand-in for the full d3 view served at /social/{token}.
|
||||
|
||||
const SVGNS = "http://www.w3.org/2000/svg";
|
||||
const PAL = {
|
||||
base: "#c9a84c", // gold
|
||||
cdn: "#00d4ff", // cyan
|
||||
ab: "#e63946", // cinnabar (anti-bot)
|
||||
opg: "#6e40c9", // void purple (operator-grade)
|
||||
eye: "#00ff41", // matrix green
|
||||
link: "#2a2a3a",
|
||||
};
|
||||
|
||||
function el(name, attrs) {
|
||||
const n = document.createElementNS(SVGNS, name);
|
||||
for (const k in attrs) n.setAttribute(k, attrs[k]);
|
||||
return n;
|
||||
}
|
||||
|
||||
function tierOf(node) {
|
||||
if (node.opgrade_vendor) return "opg";
|
||||
if (node.antibot_vendor) return "ab";
|
||||
if (node.cdn_vendor) return "cdn";
|
||||
return "base";
|
||||
}
|
||||
|
||||
function renderGraph(svg, data) {
|
||||
while (svg.firstChild) svg.removeChild(svg.firstChild);
|
||||
const W = 260, H = 180, cx = W / 2, cy = H / 2;
|
||||
|
||||
const nodes = (data && data.nodes ? data.nodes.slice() : [])
|
||||
.sort((a, b) => (b.hits || 0) - (a.hits || 0))
|
||||
.slice(0, 14);
|
||||
|
||||
if (!nodes.length) {
|
||||
const t = el("text", { x: cx, y: cy, fill: "#6b6b7a", "font-size": 11,
|
||||
"text-anchor": "middle" });
|
||||
t.textContent = "Aucun traceur détecté pour l'instant";
|
||||
svg.appendChild(t);
|
||||
return;
|
||||
}
|
||||
|
||||
const maxHits = Math.max(...nodes.map((n) => n.hits || 1));
|
||||
const R = 66;
|
||||
|
||||
// spokes first (under the dots)
|
||||
nodes.forEach((n, i) => {
|
||||
const a = (i / nodes.length) * Math.PI * 2 - Math.PI / 2;
|
||||
const x = cx + Math.cos(a) * R, y = cy + Math.sin(a) * R;
|
||||
svg.appendChild(el("line", { x1: cx, y1: cy, x2: x, y2: y,
|
||||
stroke: PAL.link, "stroke-width": 1 }));
|
||||
});
|
||||
|
||||
// tracker dots
|
||||
nodes.forEach((n, i) => {
|
||||
const a = (i / nodes.length) * Math.PI * 2 - Math.PI / 2;
|
||||
const x = cx + Math.cos(a) * R, y = cy + Math.sin(a) * R;
|
||||
const r = 3 + Math.round(6 * Math.sqrt((n.hits || 1) / maxHits));
|
||||
const fill = PAL[tierOf(n)];
|
||||
const c = el("circle", { cx: x, cy: y, r, fill, "fill-opacity": 0.85 });
|
||||
const title = el("title", {});
|
||||
title.textContent = `${n.domain} — ${n.hits || 0} hits`
|
||||
+ (n.cdn_vendor ? ` · ${n.cdn_vendor}` : "")
|
||||
+ (n.antibot_vendor ? ` · anti-bot ${n.antibot_vendor}` : "")
|
||||
+ (n.opgrade_vendor ? ` · opérateur ${n.opgrade_vendor}` : "");
|
||||
c.appendChild(title);
|
||||
svg.appendChild(c);
|
||||
});
|
||||
|
||||
// the eye (device) at the centre
|
||||
svg.appendChild(el("circle", { cx, cy, r: 13, fill: "#0c0c12",
|
||||
stroke: PAL.eye, "stroke-width": 2 }));
|
||||
svg.appendChild(el("circle", { cx, cy, r: 4.5, fill: PAL.eye }));
|
||||
}
|
||||
|
||||
globalThis.renderGraph = renderGraph;
|
||||
83
clients/webext-toolbox/popup/popup.css
Normal file
83
clients/webext-toolbox/popup/popup.css
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/* SPDX-License-Identifier: LicenseRef-CMSD-1.0 */
|
||||
/* SecuBox cyberpunk/hermetic palette (DESIGN-CHARTER) */
|
||||
:root {
|
||||
--cosmos: #0a0a0f;
|
||||
--gold: #c9a84c;
|
||||
--cinnabar: #e63946;
|
||||
--matrix: #00ff41;
|
||||
--void: #6e40c9;
|
||||
--cyan: #00d4ff;
|
||||
--text: #e8e6d9;
|
||||
--muted: #6b6b7a;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
width: 300px;
|
||||
margin: 0;
|
||||
background: var(--cosmos);
|
||||
color: var(--text);
|
||||
font: 13px/1.4 system-ui, "Segoe UI", sans-serif;
|
||||
padding: 10px 12px 8px;
|
||||
}
|
||||
header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.logo { color: var(--gold); font-weight: 700; letter-spacing: .5px; }
|
||||
.r3 {
|
||||
font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 4px;
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
.r3.on { color: var(--matrix); }
|
||||
.r3.off { color: var(--muted); }
|
||||
.muted { color: var(--muted); font-size: 11px; }
|
||||
.err { color: var(--cinnabar); font-size: 11px; min-height: 14px; }
|
||||
|
||||
label { display: block; font-size: 11px; color: var(--muted); margin: 8px 0 4px; }
|
||||
input[type=text] {
|
||||
width: 100%; padding: 7px 8px; border-radius: 6px;
|
||||
border: 1px solid #333; background: #14141c; color: var(--text);
|
||||
}
|
||||
button {
|
||||
cursor: pointer; border: 1px solid #333; border-radius: 6px;
|
||||
background: #14141c; color: var(--text); padding: 7px 8px; font-size: 12px;
|
||||
}
|
||||
button:hover { border-color: var(--gold); }
|
||||
button.go {
|
||||
width: 100%; margin-top: 8px; background: var(--gold); color: var(--cosmos);
|
||||
font-weight: 700; border-color: var(--gold);
|
||||
}
|
||||
button.danger { color: var(--cinnabar); border-color: var(--cinnabar); }
|
||||
|
||||
.stats { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 6px; margin-bottom: 8px; }
|
||||
.stat {
|
||||
background: #12121a; border: 1px solid #222; border-radius: 6px;
|
||||
padding: 6px 2px; text-align: center;
|
||||
}
|
||||
.stat b { display: block; font-size: 16px; color: var(--gold); }
|
||||
.stat span { font-size: 9px; color: var(--muted); }
|
||||
.stat.warn b { color: var(--cinnabar); }
|
||||
.stat.opg b { color: var(--void); }
|
||||
|
||||
#graph { width: 100%; height: 180px; background: #0c0c12; border-radius: 8px; display: block; }
|
||||
|
||||
.toplist { margin: 8px 0; max-height: 132px; overflow-y: auto; }
|
||||
.row {
|
||||
display: flex; align-items: center; gap: 6px; padding: 3px 2px;
|
||||
border-bottom: 1px solid #1a1a22; font-size: 11px;
|
||||
}
|
||||
.row .dom { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.row .hits { color: var(--muted); }
|
||||
.tier { font-size: 9px; padding: 1px 4px; border-radius: 3px; }
|
||||
.tier.cdn { background: #1d2a33; color: var(--cyan); }
|
||||
.tier.ab { background: #2a1416; color: var(--cinnabar); }
|
||||
.tier.opg { background: #1e1430; color: var(--void); }
|
||||
|
||||
.actions { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 6px 0; }
|
||||
.actions button:last-child { grid-column: 1 / 3; }
|
||||
|
||||
footer {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-top: 6px; padding-top: 6px; border-top: 1px solid #1a1a22;
|
||||
}
|
||||
footer a { color: var(--cyan); text-decoration: none; font-size: 11px; }
|
||||
56
clients/webext-toolbox/popup/popup.html
Normal file
56
clients/webext-toolbox/popup/popup.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
<title>SecuBox Cartographie</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<span class="logo">👁️ VILLAGE3B</span>
|
||||
<span id="r3dot" class="r3 off" title="État du tunnel R3">R3</span>
|
||||
</header>
|
||||
|
||||
<!-- Pairing (shown when no token) -->
|
||||
<section id="pair" hidden>
|
||||
<p class="muted">Connecte-toi à la cabine pour voir qui t'observe.</p>
|
||||
<label>Borne
|
||||
<input id="host" type="text" placeholder="kbin.gk2.secubox.in" autocomplete="off">
|
||||
</label>
|
||||
<button id="pairBtn" class="go">Appairer (R3)</button>
|
||||
<p id="pairMsg" class="err"></p>
|
||||
</section>
|
||||
|
||||
<!-- Live view (shown when paired) -->
|
||||
<section id="live" hidden>
|
||||
<div class="stats">
|
||||
<div class="stat"><b id="sTrackers">–</b><span>traceurs</span></div>
|
||||
<div class="stat"><b id="sSites">–</b><span>sites</span></div>
|
||||
<div class="stat warn"><b id="sAntibot">–</b><span>anti-bot</span></div>
|
||||
<div class="stat opg"><b id="sOpgrade">–</b><span>opérateur</span></div>
|
||||
</div>
|
||||
|
||||
<svg id="graph" viewBox="0 0 260 180" role="img" aria-label="Mini cartographie"></svg>
|
||||
|
||||
<div class="toplist" id="topList"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="openFull">🗺️ Cartographie complète</button>
|
||||
<button id="pdf">📄 Rapport PDF</button>
|
||||
<button id="wipe" class="danger">🧹 Effacer mes données</button>
|
||||
</div>
|
||||
<p id="liveMsg" class="muted"></p>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<a href="#" id="settings">Réglages</a>
|
||||
<span class="muted" id="ver"></span>
|
||||
</footer>
|
||||
|
||||
<script src="../api.js"></script>
|
||||
<script src="graph.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
131
clients/webext-toolbox/popup/popup.js
Normal file
131
clients/webext-toolbox/popup/popup.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
//
|
||||
// SecuBox-Deb :: webext-toolbox :: popup controller
|
||||
|
||||
const api = globalThis.SbxApi;
|
||||
const ext = api.ext;
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function show(which) {
|
||||
$("pair").hidden = which !== "pair";
|
||||
$("live").hidden = which !== "live";
|
||||
}
|
||||
|
||||
function fillTopList(nodes) {
|
||||
const list = $("topList");
|
||||
list.innerHTML = "";
|
||||
(nodes || [])
|
||||
.slice()
|
||||
.sort((a, b) => (b.hits || 0) - (a.hits || 0))
|
||||
.slice(0, 12)
|
||||
.forEach((n) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "row";
|
||||
const dom = document.createElement("span");
|
||||
dom.className = "dom";
|
||||
dom.textContent = n.domain || n.id;
|
||||
row.appendChild(dom);
|
||||
if (n.opgrade_vendor) addTier(row, "opg", n.opgrade_vendor);
|
||||
else if (n.antibot_vendor) addTier(row, "ab", n.antibot_vendor);
|
||||
else if (n.cdn_vendor) addTier(row, "cdn", n.cdn_vendor);
|
||||
const hits = document.createElement("span");
|
||||
hits.className = "hits";
|
||||
hits.textContent = (n.hits || 0) + "×";
|
||||
row.appendChild(hits);
|
||||
list.appendChild(row);
|
||||
});
|
||||
}
|
||||
function addTier(row, cls, label) {
|
||||
const t = document.createElement("span");
|
||||
t.className = "tier " + cls;
|
||||
t.textContent = label;
|
||||
row.appendChild(t);
|
||||
}
|
||||
|
||||
function paint(data) {
|
||||
const s = data.stats || {};
|
||||
$("sTrackers").textContent = s.total_trackers ?? 0;
|
||||
$("sSites").textContent = s.total_sites ?? 0;
|
||||
$("sAntibot").textContent = s.antibot_sites ?? 0;
|
||||
$("sOpgrade").textContent = s.opgrade_sites ?? 0;
|
||||
globalThis.renderGraph($("graph"), data);
|
||||
fillTopList(data.nodes);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const cfg = await api.getConfig();
|
||||
$("ver").textContent = "v" + (ext.runtime.getManifest().version || "");
|
||||
|
||||
// tunnel indicator
|
||||
api.r3Check(cfg.host).then((r) => {
|
||||
const dot = $("r3dot");
|
||||
dot.className = "r3 " + (r.tunnel ? "on" : "off");
|
||||
dot.title = r.tunnel ? `Tunnel R3 actif (${r.peer_ip || "?"})` : "Hors tunnel R3";
|
||||
});
|
||||
|
||||
if (!cfg.token) {
|
||||
$("host").value = cfg.host;
|
||||
show("pair");
|
||||
return;
|
||||
}
|
||||
show("live");
|
||||
$("liveMsg").textContent = "Chargement…";
|
||||
try {
|
||||
const data = await api.graph(cfg.host, cfg.token, cfg.since);
|
||||
paint(data);
|
||||
$("liveMsg").textContent = "";
|
||||
} catch (e) {
|
||||
if (String(e.message) === "token-expired") {
|
||||
// token died — drop it and go back to pairing
|
||||
await api.setConfig({ token: "" });
|
||||
show("pair");
|
||||
$("host").value = cfg.host;
|
||||
$("pairMsg").textContent = "Session expirée — ré-appaire.";
|
||||
} else {
|
||||
$("liveMsg").textContent = "Erreur : " + e.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── events ──
|
||||
$("pairBtn").addEventListener("click", async () => {
|
||||
const host = $("host").value.trim() || api.DEFAULTS.host;
|
||||
$("pairMsg").textContent = "Appairage…";
|
||||
try {
|
||||
await api.setConfig({ host });
|
||||
const token = await api.pair(host);
|
||||
await api.setConfig({ token });
|
||||
ext.runtime.sendMessage({ type: "refresh" });
|
||||
await load();
|
||||
} catch (e) {
|
||||
$("pairMsg").textContent = e.message + " (es-tu sur le tunnel ?)";
|
||||
}
|
||||
});
|
||||
|
||||
$("openFull").addEventListener("click", async () => {
|
||||
const cfg = await api.getConfig();
|
||||
ext.tabs.create({ url: api.socialUrl(cfg.host, cfg.token) });
|
||||
});
|
||||
$("pdf").addEventListener("click", async () => {
|
||||
const cfg = await api.getConfig();
|
||||
ext.tabs.create({ url: api.reportUrl(cfg.host, cfg.token) });
|
||||
});
|
||||
$("wipe").addEventListener("click", async () => {
|
||||
if (!confirm("Effacer toutes tes données de cartographie sur la cabine ?")) return;
|
||||
const cfg = await api.getConfig();
|
||||
try {
|
||||
const r = await api.wipe(cfg.host, cfg.token);
|
||||
$("liveMsg").textContent = `Effacé : ${r.rows_deleted ?? 0} entrées.`;
|
||||
await api.setConfig({ token: "" });
|
||||
setTimeout(load, 800);
|
||||
} catch (e) {
|
||||
$("liveMsg").textContent = "Erreur effacement : " + e.message;
|
||||
}
|
||||
});
|
||||
$("settings").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
ext.runtime.openOptionsPage();
|
||||
});
|
||||
|
||||
load();
|
||||
|
|
@ -1,3 +1,21 @@
|
|||
secubox-toolbox (2.6.14-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Serve the browser ToolBoX extension .xpi from the toolbox (#532).
|
||||
- api.py GET /wg/toolbox.xpi : serves the local .xpi
|
||||
(/var/lib/secubox/toolbox/webext/secubox-toolbox-webext.xpi) with
|
||||
content-type application/x-xpinstall ; if absent, 302 → the latest
|
||||
public GitHub release asset (button never dead-ends).
|
||||
- /wg/onboard : new "🧩 Extension navigateur (cartographie)" button
|
||||
on both the inline + _install_panels variants.
|
||||
- sbin/secubox-toolbox-fetch-xpi : pulls the latest release asset
|
||||
into the serve path (best-effort, ZIP-magic sanity check).
|
||||
- postinst : create the webext serve dir + best-effort first fetch.
|
||||
- New client clients/webext-toolbox/ (MV3 Firefox/Chromium): live
|
||||
tracker badge + popup mini Round-Eye graph over /social/* ;
|
||||
build-webext.yml publishes the .xpi on webext-v* tags.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 13 Jun 2026 10:30:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.13-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Serve the Android ToolBox APK from the toolbox (#536, follow-up #531).
|
||||
|
|
|
|||
|
|
@ -52,6 +52,13 @@ case "$1" in
|
|||
if [ -x /usr/sbin/secubox-toolbox-fetch-apk ]; then
|
||||
/usr/sbin/secubox-toolbox-fetch-apk 2>&1 | head -2 || true
|
||||
fi
|
||||
# #532 : browser extension serve dir + best-effort fetch of the
|
||||
# latest release .xpi (so GET /wg/toolbox.xpi serves it locally).
|
||||
# Non-blocking : falls back to redirecting to the public release.
|
||||
install -d -m 0755 -o secubox-toolbox -g secubox-toolbox /var/lib/secubox/toolbox/webext
|
||||
if [ -x /usr/sbin/secubox-toolbox-fetch-xpi ]; then
|
||||
/usr/sbin/secubox-toolbox-fetch-xpi 2>&1 | head -2 || true
|
||||
fi
|
||||
# /var/log/secubox is a SHARED parent traversed by many service users
|
||||
# (the aggregator runs as `secubox` and reads waf-threats.log under
|
||||
# here). It MUST be 0755 — a 0750 owned by secubox-toolbox silently
|
||||
|
|
|
|||
|
|
@ -105,6 +105,9 @@ execute_after_dh_auto_install:
|
|||
# #536 : Android APK fetch helper.
|
||||
install -m 0755 sbin/secubox-toolbox-fetch-apk \
|
||||
debian/secubox-toolbox/usr/sbin/
|
||||
# #532 : browser extension .xpi fetch helper.
|
||||
install -m 0755 sbin/secubox-toolbox-fetch-xpi \
|
||||
debian/secubox-toolbox/usr/sbin/
|
||||
install -m 0755 sbin/secubox-toolbox-wg-restore \
|
||||
debian/secubox-toolbox/usr/sbin/
|
||||
install -m 0644 systemd/secubox-toolbox-wg-restore.service \
|
||||
|
|
|
|||
44
packages/secubox-toolbox/sbin/secubox-toolbox-fetch-xpi
Executable file
44
packages/secubox-toolbox/sbin/secubox-toolbox-fetch-xpi
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
#
|
||||
# SecuBox-Deb :: secubox-toolbox-fetch-xpi (#532)
|
||||
#
|
||||
# Pull the latest browser ToolBoX extension .xpi (published as a GitHub
|
||||
# release asset by build-webext.yml on webext-v* tags) into the toolbox
|
||||
# serve path, so GET /wg/toolbox.xpi serves it locally (offline-capable
|
||||
# install from the cabine). Best-effort : a failure leaves any existing
|
||||
# .xpi in place ; the endpoint falls back to the public release redirect.
|
||||
set -euo pipefail
|
||||
readonly MODULE="secubox-toolbox-fetch-xpi"
|
||||
|
||||
DEST_DIR="/var/lib/secubox/toolbox/webext"
|
||||
DEST="${DEST_DIR}/secubox-toolbox-webext.xpi"
|
||||
RELEASE_URL="https://github.com/CyberMind-FR/secubox-deb/releases/latest/download/secubox-toolbox-webext.xpi"
|
||||
|
||||
log() { logger -t "$MODULE" -- "$*" 2>/dev/null || echo "[$MODULE] $*" >&2; }
|
||||
|
||||
install -d -m 0755 -o secubox-toolbox -g secubox-toolbox "$DEST_DIR" 2>/dev/null \
|
||||
|| mkdir -p "$DEST_DIR"
|
||||
|
||||
TMP=$(mktemp --suffix=.xpi)
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
if wget -q --timeout=20 --tries=2 "$RELEASE_URL" -O "$TMP" && [ -s "$TMP" ]; then
|
||||
# Sanity : an .xpi is a ZIP — must start with PK\x03\x04.
|
||||
if head -c 2 "$TMP" | grep -q "PK"; then
|
||||
install -m 0644 "$TMP" "$DEST"
|
||||
chown secubox-toolbox:secubox-toolbox "$DEST" 2>/dev/null || true
|
||||
log "fetched .xpi -> ${DEST} ($(stat -c%s "$DEST" 2>/dev/null) bytes)"
|
||||
exit 0
|
||||
else
|
||||
log "downloaded file is not an .xpi (no release asset yet?) — keeping existing"
|
||||
fi
|
||||
else
|
||||
log "fetch failed (no release yet / network) — /wg/toolbox.xpi will redirect to the release"
|
||||
fi
|
||||
else
|
||||
log "wget missing — cannot fetch .xpi"
|
||||
fi
|
||||
exit 0
|
||||
|
|
@ -622,6 +622,7 @@ pre{background:#1a1a25;color:var(--phos-hot);padding:0.6rem 0.8rem;border-radius
|
|||
|
||||
<div class="tab-content" data-content=android>
|
||||
<a href="/wg/toolbox.apk" class="btn btn-go">📱 Installer l'app ToolBoX (1-tap)</a>
|
||||
<a href="/wg/toolbox.xpi" class="btn">🧩 Extension navigateur (cartographie)</a>
|
||||
<div class=warn style="margin-top:0.5rem">
|
||||
✨ <b>Le plus simple</b> : l'app fait tout (CA + tunnel + vérif) en 5 étapes.
|
||||
Active « sources inconnues » à l'installation. Sinon, méthode manuelle ci-dessous :
|
||||
|
|
@ -1192,6 +1193,7 @@ _ONBOARD_BODY = {
|
|||
"android": """
|
||||
<p><b>✨ Le plus simple — l'app ToolBoX fait tout :</b></p>
|
||||
<a class=btn href="/wg/toolbox.apk">📱 Installer l'app ToolBoX (.apk, 1-tap)</a>
|
||||
<a class=btn href="/wg/toolbox.xpi">🧩 Extension navigateur (cartographie live)</a>
|
||||
<p class=note>Active « sources inconnues » à l'installation. L'app installe le CA, importe le tunnel et vérifie le R3 en 5 étapes. Sinon, méthode manuelle :</p>
|
||||
<ol>
|
||||
<li>Installe l'app <a class=btn href="https://play.google.com/store/apps/details?id=com.wireguard.android" target=_blank rel=noopener>WireGuard</a> depuis le Play Store.</li>
|
||||
|
|
@ -1365,6 +1367,34 @@ async def wg_toolbox_apk() -> Response:
|
|||
return RedirectResponse(url=_ANDROID_APK_RELEASE, status_code=302)
|
||||
|
||||
|
||||
# Browser extension (Firefox .xpi), same serve pattern as the APK (#532).
|
||||
_WEBEXT_XPI = Path("/var/lib/secubox/toolbox/webext/secubox-toolbox-webext.xpi")
|
||||
_WEBEXT_XPI_RELEASE = (
|
||||
"https://github.com/CyberMind-FR/secubox-deb/releases/latest/download/"
|
||||
"secubox-toolbox-webext.xpi"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/wg/toolbox.xpi")
|
||||
async def wg_toolbox_xpi() -> Response:
|
||||
"""Serve the browser ToolBoX extension .xpi (#532).
|
||||
|
||||
Local file first (install from the cabine, works offline) ; if it
|
||||
hasn't been fetched yet, 302 to the latest public GitHub release
|
||||
asset so the onboard button never dead-ends.
|
||||
"""
|
||||
if _WEBEXT_XPI.exists() and _WEBEXT_XPI.stat().st_size > 0:
|
||||
return Response(
|
||||
content=_WEBEXT_XPI.read_bytes(),
|
||||
media_type="application/x-xpinstall",
|
||||
headers={
|
||||
"Content-Disposition": "attachment; filename=secubox-toolbox-webext.xpi",
|
||||
"Cache-Control": "public, max-age=300",
|
||||
},
|
||||
)
|
||||
return RedirectResponse(url=_WEBEXT_XPI_RELEASE, status_code=302)
|
||||
|
||||
|
||||
@router.get("/wg/ca.mobileconfig")
|
||||
async def wg_ca_mobileconfig() -> Response:
|
||||
"""iOS profile that installs the mitm-wg CA in trust store."""
|
||||
|
|
|
|||
86
wiki/Browser-Extension.md
Normal file
86
wiki/Browser-Extension.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
# 🧩 Browser extension — Cartographie sociale live
|
||||
|
||||
The **SecuBox ToolBoX browser extension** *emancipates* the R3 toolbox live
|
||||
tracker analysis into your browser. Instead of only seeing the *cartographie
|
||||
sociale* on `kbin/social/me`, a toolbar badge ticks up as trackers fire and a
|
||||
popup shows who is watching you — live, as you browse.
|
||||
|
||||
Sibling of the [[Android-ToolBox]] app. Talks **only** to your cabine over the
|
||||
R3 tunnel — no third-party calls.
|
||||
|
||||
- Source : [`clients/webext-toolbox/`](https://github.com/CyberMind-FR/secubox-deb/tree/master/clients/webext-toolbox)
|
||||
- WebExtension **MV3** (Firefox `.xpi` + Chromium) · plain JS/HTML/CSS, no bundler
|
||||
- License : `LicenseRef-CMSD-1.0`
|
||||
|
||||
## Install
|
||||
|
||||
The toolbox serves the built extension:
|
||||
|
||||
```
|
||||
https://kbin.<board>.secubox.in/wg/toolbox.xpi
|
||||
```
|
||||
|
||||
The kbin onboard panel exposes a **🧩 Extension navigateur (cartographie)**
|
||||
button. When a local build is present the cabine serves it
|
||||
(`application/x-xpinstall`); otherwise it 302-redirects to the latest GitHub
|
||||
release asset `secubox-toolbox-webext.xpi`.
|
||||
|
||||
- **Firefox** — open the `.xpi`. A permanent install needs an AMO-signed build
|
||||
(release CI / `web-ext sign`); for development use *about:debugging → Load
|
||||
Temporary Add-on*, or an ESR/Dev build with
|
||||
`xpinstall.signatures.required=false`.
|
||||
- **Chromium** — load unpacked (`chrome://extensions` → Developer mode).
|
||||
Chromium action icons must be raster — rasterise `icons/icon.svg` to PNG
|
||||
before a Web Store build (Firefox accepts the SVG as-is).
|
||||
|
||||
## What it does
|
||||
|
||||
- **Pairing** — calls `/social/me` over the tunnel, which 303-redirects to
|
||||
`/social/{token}`; the extension reads the minted HMAC token from the final
|
||||
URL. Anonymous (rotating `mac_hash`), no account. Manual token entry in the
|
||||
options page.
|
||||
- **Live badge** — the toolbar icon shows the live tracker count (polled once a
|
||||
minute). Colour escalates: 🟡 gold → 🟥 anti-bot present → 🟪 operator-grade
|
||||
present.
|
||||
- **Popup** — four stat tiles (trackers / sites / anti-bot / operator-grade), a
|
||||
dependency-free **mini Round-Eye graph** (device centre, trackers on the ring,
|
||||
radius by hits, colour by tier), and a top-tracker list tagged with CDN
|
||||
(12.A) / anti-bot (12.B) / operator-grade (12.C).
|
||||
- **Actions** — *Cartographie complète* (full d3 view at `/social/{token}`),
|
||||
*Rapport PDF* (`/social/report/{token}.pdf`), *Effacer mes données* (RGPD
|
||||
art. 17 wipe → `POST /social/wipe/{token}`).
|
||||
|
||||
## Build (CI)
|
||||
|
||||
No bundler — `build-webext.yml` runs `web-ext lint` then packages the `.xpi`:
|
||||
|
||||
- artifact on push to `master` / PRs touching `clients/webext-toolbox/**`
|
||||
- tagging `webext-v*` publishes the `.xpi` as a release asset
|
||||
|
||||
Locally:
|
||||
|
||||
```bash
|
||||
cd clients/webext-toolbox
|
||||
./build.sh # → secubox-toolbox-webext-<version>.xpi
|
||||
```
|
||||
|
||||
## Cabine endpoints consumed
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `/wg/r3-check` | tunnel presence indicator |
|
||||
| `/social/me` | pair → mint token (303 → `/social/{token}`) |
|
||||
| `/social/graph/{token}?since=` | per-session tracker graph JSON |
|
||||
| `/social/wipe/{token}` | RGPD art. 17 erasure |
|
||||
| `/social/{token}` | full d3 cartographie page |
|
||||
| `/social/report/{token}.pdf` | bilingual PDF report |
|
||||
| `/wg/toolbox.xpi` | the extension itself |
|
||||
|
||||
## Notes
|
||||
|
||||
- No server-side CORS needed: an MV3 extension with `host_permissions` for
|
||||
`*.secubox.in` fetches cross-origin from its background without CORS.
|
||||
- MVP polls `/social/graph` and computes the delta client-side; a future
|
||||
`GET /social/live/{token}` (SSE) can replace the poll. The deception-plane
|
||||
*Poke/Emancipate* per-site control lands once the deception plane ships.
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
* [[ESPRESSObin]] | [FR](ESPRESSObin-FR) | [DE](ESPRESSObin-DE) | [中文](ESPRESSObin-ZH)
|
||||
* [[Eye-Remote]] 📡
|
||||
* [[Android-ToolBox]] 📱 one-tap R3
|
||||
* [[Browser-Extension]] 🧩 cartographie
|
||||
* [[QEMU-ARM64]] 🖥️
|
||||
|
||||
### 🟢 ROOT — Configuration
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user