Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f958e30653 | |||
| fb4502f79a | |||
| 869726ce19 | |||
| 66b58891a3 | |||
| 7d85dec970 | |||
| 98b6769256 | |||
| ae6ea536fc | |||
| 7eb3f2e1b3 | |||
| c730657605 | |||
| 610d6161e8 | |||
| 84b1de016b |
@@ -1,5 +1,4 @@
|
|||||||
# [DЯΣΛMMΛKΣЯ]
|
# OVERWATCH
|
||||||
## . //0VΣЯW4TCH
|
|
||||||
|
|
||||||
A native Android (Kotlin) **passive surveillance-detection** app. Open it, hit
|
A native Android (Kotlin) **passive surveillance-detection** app. Open it, hit
|
||||||
**START**, and a circle turns **green / yellow / orange / red** depending on
|
**START**, and a circle turns **green / yellow / orange / red** depending on
|
||||||
@@ -13,7 +12,21 @@ on upward escalations — you don't have to be looking at the screen.
|
|||||||
> advertise/fuzz code from one of the reference projects is intentionally
|
> advertise/fuzz code from one of the reference projects is intentionally
|
||||||
> excluded.
|
> excluded.
|
||||||
|
|
||||||
Latest release: [v0.5.2](https://github.com/KaraZajac/OVERWATCH/releases) (debug-signed APK, sideload).
|
Website: **[overwatch.netslum.io](https://overwatch.netslum.io)** · Latest release: [v0.5.3](https://github.com/KaraZajac/OVERWATCH/releases) (debug-signed APK, sideload).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/img/overwatch-main.png" width="240" alt="Main screen — live threat-map circle" />
|
||||||
|
|
||||||
|
<img src="docs/img/overwatch-sources.png" width="240" alt="Detection-source drill-down" />
|
||||||
|
|
||||||
|
<img src="docs/img/overwatch-settings.png" width="240" alt="Settings" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center"><sub>Live threat-map circle · source drill-down · settings</sub></p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -26,7 +39,7 @@ Latest release: [v0.5.2](https://github.com/KaraZajac/OVERWATCH/releases) (debug
|
|||||||
| **DEFLOCK** | Crowdsourced ALPR locations within configurable proximity (default 200 m) | POST to Overpass API (`overpass.deflock.org` → fallback `overpass-api.de`) for `man_made=surveillance + surveillance:type=ALPR` in a 5 km bbox; 24 h on-disk cache by 0.05° grid cell. Refetches when the user moves > 1.5 km from the last fetch center. Backoffs after Overpass failures; treats `{"remark": "...timed out..."}` 200-responses as failure so timeouts don't poison the cache. |
|
| **DEFLOCK** | Crowdsourced ALPR locations within configurable proximity (default 200 m) | POST to Overpass API (`overpass.deflock.org` → fallback `overpass-api.de`) for `man_made=surveillance + surveillance:type=ALPR` in a 5 km bbox; 24 h on-disk cache by 0.05° grid cell. Refetches when the user moves > 1.5 km from the last fetch center. Backoffs after Overpass failures; treats `{"remark": "...timed out..."}` 200-responses as failure so timeouts don't poison the cache. |
|
||||||
| **CITIZEN** | Real-time public-safety incidents (police-relevant only — fire/medical-only events filtered out) within configurable proximity, < 30 min old | `citizen.com/api/incident/trending` (bbox) polled every 60 s, then per-incident detail via `/api/incident/{id}` with an in-memory cache so each incident is fetched once per session. First poll fires immediately on the first location fix. |
|
| **CITIZEN** | Real-time public-safety incidents (police-relevant only — fire/medical-only events filtered out) within configurable proximity, < 30 min old | `citizen.com/api/incident/trending` (bbox) polled every 60 s, then per-incident detail via `/api/incident/{id}` with an in-memory cache so each incident is fetched once per session. First poll fires immediately on the first location fix. |
|
||||||
| **WAZE** | User-reported `POLICE` alerts still active in the feed within configurable proximity (default 500 m), up to ~45 min old | `api.blackflagintel.com/waze/alerts-and-jams` — the OVERWATCH proxy (Caddy) that injects the OpenWeb Ninja key server-side and forwards to their hosted Waze scrape, sidestepping the reCAPTCHA gating that 403s direct `live-map/api/georss` calls. The app authenticates with an `X-App-Token` entered in Settings (encrypted on-device); the paid key never ships in the APK. Polled every ~4 min. Upstream ignores type filtering and caps at 200 alerts, so the client pulls the full page and filters to `POLICE` itself. Alerts carry confidence (0–5) + reliability (0–10); high values nudge the score up. No token → source shows "not configured" in the drill-down. |
|
| **WAZE** | User-reported `POLICE` alerts still active in the feed within configurable proximity (default 500 m), up to ~45 min old | `api.blackflagintel.com/waze/alerts-and-jams` — the OVERWATCH proxy (Caddy) that injects the OpenWeb Ninja key server-side and forwards to their hosted Waze scrape, sidestepping the reCAPTCHA gating that 403s direct `live-map/api/georss` calls. The app authenticates with an `X-App-Token` entered in Settings (encrypted on-device); the paid key never ships in the APK. Polled every ~4 min. Upstream ignores type filtering and caps at 200 alerts, so the client pulls the full page and filters to `POLICE` itself. Alerts carry confidence (0–5) + reliability (0–10); high values nudge the score up. No token → source shows "not configured" in the drill-down. |
|
||||||
| **COMMERCIAL** | Nearby consumer smart-home / voice-assistant gear (Nest, Ring, Echo, hidden cams) as a secondary situational signal | Rides the BLE + WiFi scans — OUI / device-name / service-UUID / SSID matches from `MicTargets`. Score-capped at ORANGE so a cluster of doorbells never reads as ALPR-grade certainty. |
|
| **COMMERCIAL** | Nearby consumer smart-home / voice gear (Nest, Ring, Echo, hidden cams) and camera-bearing smart glasses (Meta, Snap, Vuzix) as a secondary situational signal | Rides the BLE + WiFi scans — OUI / device-name / service-UUID / SSID matches plus Bluetooth SIG company IDs from `MicTargets`. Score-capped at ORANGE so a cluster of doorbells (or a passing pair of Ray-Bans) never reads as ALPR-grade certainty. |
|
||||||
|
|
||||||
> **Waze is back (v0.4.0+), via a key-protected proxy.** Waze reCAPTCHA-gated its `live-map/api/georss` endpoint in 2025/2026 — automated calls get HTTP 403 regardless of IP or headless-vs-headful browser (it scores browser *reputation*, verified by direct testing), which is why v0.1.5 removed the original integration and why no free scraper survives. OVERWATCH reads Waze POLICE alerts through [OpenWeb Ninja](https://www.openwebninja.com)'s hosted feed (pay-as-you-go ~$0.005/req, ≈ $1–3/mo at the 4-min poll). To keep the paid key off every device, the app doesn't hold it: a Caddy reverse proxy at `api.blackflagintel.com` injects the key server-side, and the app authenticates with a scoped, revocable `X-App-Token` entered in Settings (stored encrypted via the Android Keystore). The Waze for Cities partner feed was ruled out — it excludes POLICE and is agency-only. Waze complements Citizen: denser for roadway stops / speed traps.
|
> **Waze is back (v0.4.0+), via a key-protected proxy.** Waze reCAPTCHA-gated its `live-map/api/georss` endpoint in 2025/2026 — automated calls get HTTP 403 regardless of IP or headless-vs-headful browser (it scores browser *reputation*, verified by direct testing), which is why v0.1.5 removed the original integration and why no free scraper survives. OVERWATCH reads Waze POLICE alerts through [OpenWeb Ninja](https://www.openwebninja.com)'s hosted feed (pay-as-you-go ~$0.005/req, ≈ $1–3/mo at the 4-min poll). To keep the paid key off every device, the app doesn't hold it: a Caddy reverse proxy at `api.blackflagintel.com` injects the key server-side, and the app authenticates with a scoped, revocable `X-App-Token` entered in Settings (stored encrypted via the Android Keystore). The Waze for Cities partner feed was ruled out — it excludes POLICE and is agency-only. Waze complements Citizen: denser for roadway stops / speed traps.
|
||||||
|
|
||||||
@@ -212,6 +225,7 @@ field-tested. Current release **v0.5.1**. Notable changes:
|
|||||||
- v0.5.0 — Waze re-added via a key-protected Caddy proxy (`api.blackflagintel.com`): the OpenWeb Ninja key stays server-side, the app uses an encrypted per-device token. GitHub Actions release pipeline added.
|
- v0.5.0 — Waze re-added via a key-protected Caddy proxy (`api.blackflagintel.com`): the OpenWeb Ninja key stays server-side, the app uses an encrypted per-device token. GitHub Actions release pipeline added.
|
||||||
- v0.5.1 — UI: larger map circle with a threat-color ring, ⌖ user crosshair, source-color dots (Flock red / Waze blue / Citizen purple), START moved to the bottom.
|
- v0.5.1 — UI: larger map circle with a threat-color ring, ⌖ user crosshair, source-color dots (Flock red / Waze blue / Citizen purple), START moved to the bottom.
|
||||||
- v0.5.2 — Committed a fixed debug keystore so CI + local builds sign identically; updates now install in place (no functional changes).
|
- v0.5.2 — Committed a fixed debug keystore so CI + local builds sign identically; updates now install in place (no functional changes).
|
||||||
|
- v0.5.3 — Detect Meta / Snap / Vuzix smart glasses in the COMMERCIAL source (BLE company-id + name vectors); new radar app icon (launcher, themed, and notification).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "org.soulstone.overwatch"
|
applicationId = "org.soulstone.overwatch"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 18
|
versionCode = 19
|
||||||
versionName = "0.5.2"
|
versionName = "0.5.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixed debug keystore committed to the repo (a debug key is non-secret — its
|
// Fixed debug keystore committed to the repo (a debug key is non-secret — its
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ import java.util.UUID
|
|||||||
/**
|
/**
|
||||||
* Curated targets for "device with a microphone in your space" detection.
|
* Curated targets for "device with a microphone in your space" detection.
|
||||||
*
|
*
|
||||||
* Scope is intentionally narrow — only well-known smart-home OEMs whose devices
|
* Scope is intentionally narrow — well-known smart-home OEMs whose devices stay
|
||||||
* stay in fixed locations and continuously listen. Apple manufacturer id 0x004C
|
* in fixed locations and continuously listen, plus body-worn smart glasses
|
||||||
* is deliberately excluded because every iPhone, AirPod, and Apple Watch
|
* (Meta Ray-Ban, Snap Spectacles) that record the space around them. Apple
|
||||||
* advertises it; a coffee shop full of phones must not light up the alarm.
|
* manufacturer id 0x004C is deliberately excluded because every iPhone, AirPod,
|
||||||
|
* and Apple Watch advertises it; a coffee shop full of phones must not light up
|
||||||
|
* the alarm.
|
||||||
*
|
*
|
||||||
* Detection vectors collected from public OUI registries (Wireshark/IEEE)
|
* Detection vectors collected from public OUI registries (Wireshark/IEEE)
|
||||||
* and device-setup advertisement docs.
|
* and device-setup advertisement docs.
|
||||||
*/
|
*/
|
||||||
object MicTargets {
|
object MicTargets {
|
||||||
|
|
||||||
enum class Family { ECHO, RING, GOOGLE, HIDDEN_CAM }
|
enum class Family { ECHO, RING, GOOGLE, HIDDEN_CAM, GLASSES }
|
||||||
|
|
||||||
/** Bluetooth SIG company identifiers for "voice/smart-home" device families. */
|
/** Bluetooth SIG company identifiers for "voice/smart-home" device families. */
|
||||||
private val MFG_GOOGLE = 0x00E0
|
private val MFG_GOOGLE = 0x00E0
|
||||||
@@ -23,6 +25,25 @@ object MicTargets {
|
|||||||
/** Yingxin / cheap-spy-cam mfg id seen in field reports. */
|
/** Yingxin / cheap-spy-cam mfg id seen in field reports. */
|
||||||
private val MFG_YINGXIN = 0x05A7
|
private val MFG_YINGXIN = 0x05A7
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bluetooth SIG company identifiers for camera-bearing smart glasses. The
|
||||||
|
* manufacturer-id field is the reliable vector — device names and service
|
||||||
|
* UUIDs are inconsistent across advertising frames (per the open-source
|
||||||
|
* Nearby Glasses project, which this mirrors). Caveat: these ids are
|
||||||
|
* manufacturer-wide, so Meta's also match Quest VR headsets (still a
|
||||||
|
* camera/mic device, so an acceptable secondary signal). Luxottica in BLE
|
||||||
|
* mfg data is essentially only their *smart* eyewear — plain sunglasses
|
||||||
|
* have no radio. Brands without a dedicated SIG company id (RayNeo — which
|
||||||
|
* rides under TCL's 0x0BC6, too broad to use — plus XREAL, Rokid) advertise
|
||||||
|
* under a chipset vendor's id, so those are caught by distinctive BLE-name
|
||||||
|
* hints below instead.
|
||||||
|
*/
|
||||||
|
private val MFG_META = 0x01AB // Meta Platforms, Inc. (ex-Facebook)
|
||||||
|
private val MFG_META_TECH = 0x058E // Meta Platforms Technologies (Reality Labs; also Quest)
|
||||||
|
private val MFG_LUXOTTICA = 0x0D53 // Luxottica — Ray-Ban / Oakley Meta frames
|
||||||
|
private val MFG_SNAP = 0x03C2 // Snap Inc. — Spectacles
|
||||||
|
private val MFG_VUZIX = 0x060C // Vuzix — enterprise / AR smart glasses
|
||||||
|
|
||||||
/** Echo/Alexa Voice Service GATT (FE03 — assigned to Amazon Lab126). */
|
/** Echo/Alexa Voice Service GATT (FE03 — assigned to Amazon Lab126). */
|
||||||
private val UUID_AVS = UUID.fromString("0000fe03-0000-1000-8000-00805f9b34fb")
|
private val UUID_AVS = UUID.fromString("0000fe03-0000-1000-8000-00805f9b34fb")
|
||||||
|
|
||||||
@@ -63,7 +84,13 @@ object MicTargets {
|
|||||||
"Nest" to Family.GOOGLE,
|
"Nest" to Family.GOOGLE,
|
||||||
"GoogleHome" to Family.GOOGLE,
|
"GoogleHome" to Family.GOOGLE,
|
||||||
"Chromecast" to Family.GOOGLE,
|
"Chromecast" to Family.GOOGLE,
|
||||||
"Google-Home" to Family.GOOGLE
|
"Google-Home" to Family.GOOGLE,
|
||||||
|
"Spectacles" to Family.GLASSES,
|
||||||
|
"Ray-Ban" to Family.GLASSES,
|
||||||
|
"RayNeo" to Family.GLASSES,
|
||||||
|
"Vuzix" to Family.GLASSES,
|
||||||
|
"XREAL" to Family.GLASSES,
|
||||||
|
"Rokid" to Family.GLASSES
|
||||||
)
|
)
|
||||||
|
|
||||||
private val SSID_HINTS: List<Pair<String, Family>> = listOf(
|
private val SSID_HINTS: List<Pair<String, Family>> = listOf(
|
||||||
@@ -115,6 +142,7 @@ object MicTargets {
|
|||||||
MFG_AMAZON -> Family.ECHO
|
MFG_AMAZON -> Family.ECHO
|
||||||
MFG_GOOGLE -> Family.GOOGLE
|
MFG_GOOGLE -> Family.GOOGLE
|
||||||
MFG_YINGXIN -> Family.HIDDEN_CAM
|
MFG_YINGXIN -> Family.HIDDEN_CAM
|
||||||
|
MFG_META, MFG_META_TECH, MFG_LUXOTTICA, MFG_SNAP, MFG_VUZIX -> Family.GLASSES
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,5 +177,6 @@ object MicTargets {
|
|||||||
Family.RING -> "Ring"
|
Family.RING -> "Ring"
|
||||||
Family.GOOGLE -> "Google Nest / Home"
|
Family.GOOGLE -> "Google Nest / Home"
|
||||||
Family.HIDDEN_CAM -> "Possible hidden mic / cam"
|
Family.HIDDEN_CAM -> "Possible hidden mic / cam"
|
||||||
|
Family.GLASSES -> "Smart glasses"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ class DetectionService : LifecycleService() {
|
|||||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setSmallIcon(android.R.drawable.ic_menu_view)
|
.setSmallIcon(R.drawable.ic_stat_radar)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setContentIntent(pi)
|
.setContentIntent(pi)
|
||||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ fun SettingsScreen(
|
|||||||
SourceToggle("DEFLOCK • ALPR map (Overpass)", deflock) { settings.setDeflockEnabled(it) }
|
SourceToggle("DEFLOCK • ALPR map (Overpass)", deflock) { settings.setDeflockEnabled(it) }
|
||||||
SourceToggle("CITIZEN • Real-time incident feed", citizen) { settings.setCitizenEnabled(it) }
|
SourceToggle("CITIZEN • Real-time incident feed", citizen) { settings.setCitizenEnabled(it) }
|
||||||
SourceToggle("WAZE • Live police reports", waze) { settings.setWazeEnabled(it) }
|
SourceToggle("WAZE • Live police reports", waze) { settings.setWazeEnabled(it) }
|
||||||
SourceToggle("COMMERCIAL • Nest, Ring, Echo", mic) { settings.setMicEnabled(it) }
|
SourceToggle("COMMERCIAL • Nest, Ring, Echo, glasses", mic) { settings.setMicEnabled(it) }
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
Button(
|
Button(
|
||||||
|
|||||||
@@ -1,13 +1,76 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Radar: range rings + crosshair + sweep beam + a red contact blip. Drawn on
|
||||||
|
top of the @color/bg_dark adaptive background. -->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
|
|
||||||
|
<!-- lit screen glow -->
|
||||||
|
<path android:pathData="M54,54 m-36,0 a36,36 0 1,0 72,0 a36,36 0 1,0 -72,0">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient android:type="radial"
|
||||||
|
android:centerX="54" android:centerY="54" android:gradientRadius="36">
|
||||||
|
<item android:offset="0.0" android:color="#123A24" />
|
||||||
|
<item android:offset="1.0" android:color="#000B0E12" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
|
||||||
|
<!-- sweep afterglow wedge -->
|
||||||
<path
|
<path
|
||||||
|
android:pathData="M54,54 L54,19 A35,35 0 0,1 83.3,35 Z"
|
||||||
android:fillColor="#1FAA59"
|
android:fillColor="#1FAA59"
|
||||||
android:pathData="M54,30 m-18,0 a18,18 0 1,0 36,0 a18,18 0 1,0 -36,0" />
|
android:fillAlpha="0.32" />
|
||||||
|
|
||||||
|
<!-- range rings -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#F4F6FA"
|
android:pathData="M54,54 m-34,0 a34,34 0 1,0 68,0 a34,34 0 1,0 -68,0"
|
||||||
android:pathData="M54,30 m-6,0 a6,6 0 1,0 12,0 a6,6 0 1,0 -12,0" />
|
android:strokeColor="#1FAA59"
|
||||||
|
android:strokeWidth="2" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 m-23,0 a23,23 0 1,0 46,0 a23,23 0 1,0 -46,0"
|
||||||
|
android:strokeColor="#1FAA59"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeAlpha="0.85" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 m-12,0 a12,12 0 1,0 24,0 a12,12 0 1,0 -24,0"
|
||||||
|
android:strokeColor="#1FAA59"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeAlpha="0.85" />
|
||||||
|
|
||||||
|
<!-- crosshair -->
|
||||||
|
<path
|
||||||
|
android:pathData="M20,54 L88,54"
|
||||||
|
android:strokeColor="#1FAA59"
|
||||||
|
android:strokeWidth="1.4"
|
||||||
|
android:strokeAlpha="0.55" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,20 L54,88"
|
||||||
|
android:strokeColor="#1FAA59"
|
||||||
|
android:strokeWidth="1.4"
|
||||||
|
android:strokeAlpha="0.55" />
|
||||||
|
|
||||||
|
<!-- leading sweep beam -->
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 L83.3,35"
|
||||||
|
android:strokeColor="#33E38C"
|
||||||
|
android:strokeWidth="2.4"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
|
||||||
|
<!-- contact blip -->
|
||||||
|
<path
|
||||||
|
android:pathData="M72,40 m-7,0 a7,7 0 1,0 14,0 a7,7 0 1,0 -14,0"
|
||||||
|
android:fillColor="#D7263D"
|
||||||
|
android:fillAlpha="0.22" />
|
||||||
|
<path
|
||||||
|
android:pathData="M72,40 m-3.3,0 a3.3,3.3 0 1,0 6.6,0 a3.3,3.3 0 1,0 -6.6,0"
|
||||||
|
android:fillColor="#D7263D" />
|
||||||
|
|
||||||
|
<!-- hub -->
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 m-2.4,0 a2.4,2.4 0 1,0 4.8,0 a2.4,2.4 0 1,0 -4.8,0"
|
||||||
|
android:fillColor="#33E38C" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Single-colour radar silhouette for Android 13+ themed icons (system tints it). -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 m-34,0 a34,34 0 1,0 68,0 a34,34 0 1,0 -68,0"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="2.2" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 m-23,0 a23,23 0 1,0 46,0 a23,23 0 1,0 -46,0"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="2.2" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 m-12,0 a12,12 0 1,0 24,0 a12,12 0 1,0 -24,0"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="2.2" />
|
||||||
|
<path
|
||||||
|
android:pathData="M20,54 L88,54"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="1.8" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,20 L54,88"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="1.8" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 L83.3,35"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="2.6"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M72,40 m-3.6,0 a3.6,3.6 0 1,0 7.2,0 a3.6,3.6 0 1,0 -7.2,0"
|
||||||
|
android:fillColor="#FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:pathData="M54,54 m-2.6,0 a2.6,2.6 0 1,0 5.2,0 a2.6,2.6 0 1,0 -5.2,0"
|
||||||
|
android:fillColor="#FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Status-bar / notification small icon. White silhouette; the system tints it. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,12 m-10,0 a10,10 0 1,0 20,0 a10,10 0 1,0 -20,0"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="1.4" />
|
||||||
|
<path
|
||||||
|
android:pathData="M12,12 m-5.2,0 a5.2,5.2 0 1,0 10.4,0 a5.2,5.2 0 1,0 -10.4,0"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="1.4" />
|
||||||
|
<path
|
||||||
|
android:pathData="M2.5,12 L21.5,12"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="1.1" />
|
||||||
|
<path
|
||||||
|
android:pathData="M12,2.5 L12,21.5"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="1.1" />
|
||||||
|
<path
|
||||||
|
android:pathData="M12,12 L20,7.5"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:strokeWidth="1.6"
|
||||||
|
android:strokeLineCap="round" />
|
||||||
|
<path
|
||||||
|
android:pathData="M16.4,8.9 m-1.8,0 a1.8,1.8 0 1,0 3.6,0 a1.8,1.8 0 1,0 -3.6,0"
|
||||||
|
android:fillColor="#FFFFFF" />
|
||||||
|
</vector>
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/bg_dark" />
|
<background android:drawable="@color/bg_dark" />
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/bg_dark" />
|
<background android:drawable="@color/bg_dark" />
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/* OVERWATCH landing — count-up stats + scroll reveal. No network, no deps. */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
|
||||||
|
/* count-up numbers when the stats row scrolls into view */
|
||||||
|
function countUp(el) {
|
||||||
|
const target = parseInt(el.dataset.target, 10);
|
||||||
|
if (isNaN(target)) return;
|
||||||
|
if (!target || reduceMotion) { el.textContent = String(target); return; }
|
||||||
|
const dur = 1100, start = performance.now();
|
||||||
|
(function tick(now) {
|
||||||
|
const p = Math.min(1, (now - start) / dur);
|
||||||
|
el.textContent = String(Math.round(target * (1 - Math.pow(1 - p, 3))));
|
||||||
|
if (p < 1) requestAnimationFrame(tick);
|
||||||
|
})(performance.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
const statsRow = document.getElementById('stats-row');
|
||||||
|
if (statsRow) {
|
||||||
|
const nums = statsRow.querySelectorAll('.num[data-target]');
|
||||||
|
if ('IntersectionObserver' in window && !reduceMotion) {
|
||||||
|
const io = new IntersectionObserver((entries, obs) => {
|
||||||
|
entries.forEach((e) => {
|
||||||
|
if (e.isIntersecting) { nums.forEach(countUp); obs.disconnect(); }
|
||||||
|
});
|
||||||
|
}, { threshold: 0.4 });
|
||||||
|
io.observe(statsRow);
|
||||||
|
} else {
|
||||||
|
nums.forEach((el) => { el.textContent = el.dataset.target; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* scroll-triggered reveal */
|
||||||
|
const reveals = document.querySelectorAll('.reveal');
|
||||||
|
if (!('IntersectionObserver' in window) || reduceMotion) {
|
||||||
|
reveals.forEach((el) => el.classList.add('in'));
|
||||||
|
} else {
|
||||||
|
const ro = new IntersectionObserver((entries, obs) => {
|
||||||
|
entries.forEach((e) => {
|
||||||
|
if (e.isIntersecting) { e.target.classList.add('in'); obs.unobserve(e.target); }
|
||||||
|
});
|
||||||
|
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
|
||||||
|
reveals.forEach((el) => ro.observe(el));
|
||||||
|
}
|
||||||
|
})();
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
+506
@@ -0,0 +1,506 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OVERWATCH — passive surveillance detection for Android</title>
|
||||||
|
<meta name="description" content="A native Android app that passively flags nearby Flock Safety ALPRs, Axon body cameras, and police presence. Fuses BLE/WiFi radio scans, the DeFlock ALPR map, and Citizen/Waze feeds into a green→red threat tier. Listens only — never transmits.">
|
||||||
|
<meta property="og:title" content="OVERWATCH — passive surveillance detection">
|
||||||
|
<meta property="og:description" content="Android app that flags nearby Flock ALPRs, Axon body cams, and police by fusing BLE/WiFi + the DeFlock map + Citizen/Waze. Passive only — it never transmits.">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="https://overwatch.netslum.io/">
|
||||||
|
<meta property="og:image" content="https://overwatch.netslum.io/og.png">
|
||||||
|
<meta property="og:image:width" content="1200">
|
||||||
|
<meta property="og:image:height" content="630">
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:image" content="https://overwatch.netslum.io/og.png">
|
||||||
|
<meta name="theme-color" content="#07070d">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="logo.svg">
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="style.css?v=2">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="bg-mesh" aria-hidden="true">
|
||||||
|
<div class="mesh-blob mesh-blob-1"></div>
|
||||||
|
<div class="mesh-blob mesh-blob-2"></div>
|
||||||
|
<div class="mesh-blob mesh-blob-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="nav">
|
||||||
|
<div class="container nav-inner">
|
||||||
|
<a class="nav-brand" href="#">
|
||||||
|
<img class="nav-logo" src="logo.svg" width="30" height="30" alt="" aria-hidden="true">
|
||||||
|
OVERWATCH
|
||||||
|
</a>
|
||||||
|
<div class="nav-links">
|
||||||
|
<a href="#detect">How it works</a>
|
||||||
|
<a href="#sources">Sources</a>
|
||||||
|
<a href="#tiers">Tiers</a>
|
||||||
|
<a href="#screens">Screens</a>
|
||||||
|
<a href="#download">Download</a>
|
||||||
|
<a class="nav-github" href="https://github.com/KaraZajac/OVERWATCH" aria-label="GitHub">
|
||||||
|
<svg height="18" viewBox="0 0 16 16" width="18" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- ──────────────── HERO ──────────────── -->
|
||||||
|
<header class="hero">
|
||||||
|
<div class="container hero-inner">
|
||||||
|
<div class="hero-eyebrow">
|
||||||
|
<span class="dot dot-pulse"></span>
|
||||||
|
v0.5.3 · Android · passive
|
||||||
|
</div>
|
||||||
|
<h1 class="hero-title">
|
||||||
|
<span class="display-wordmark">OVERWATCH</span>
|
||||||
|
</h1>
|
||||||
|
<p class="hero-tag">
|
||||||
|
A native Android app that <strong>passively detects surveillance around you</strong>.
|
||||||
|
Open it, hit <strong>START</strong>, and a ring turns green → yellow →
|
||||||
|
orange → red as the engine gets more sure there's a Flock Safety ALPR,
|
||||||
|
an Axon body camera, or active police nearby.
|
||||||
|
<span class="hero-tag-pop">It only listens. It never transmits, probes, or jams.</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="phone-hero">
|
||||||
|
<img class="phone" src="img/overwatch-main.png" width="1096" height="2560"
|
||||||
|
alt="OVERWATCH main screen: a live map inside a green "all clear" threat ring, centered on the user with a crosshair, and a STOP button." loading="eager">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats-row" id="stats-row">
|
||||||
|
<div class="stat-chip"><span class="num" data-target="6">0</span><span>fused sources</span></div>
|
||||||
|
<div class="stat-chip stat-vfy"><span class="num" data-target="4">0</span><span>threat tiers</span></div>
|
||||||
|
<div class="stat-chip"><span class="num" data-target="0">0</span><span>packets sent</span></div>
|
||||||
|
<div class="stat-chip"><span class="num" data-target="17">0</span><span>releases</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cta-row">
|
||||||
|
<a class="btn btn-primary" href="https://github.com/KaraZajac/OVERWATCH/releases/latest">
|
||||||
|
↓ Download APK
|
||||||
|
</a>
|
||||||
|
<a class="btn" href="#detect">How it works</a>
|
||||||
|
<a class="btn btn-ghost" href="https://github.com/KaraZajac/OVERWATCH">
|
||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
|
||||||
|
Source on GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="hero-warn">Passive defense only. Debug-signed APK — sideload; no Play Store. <a href="https://github.com/KaraZajac/OVERWATCH">Read the README.</a></p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- ──────────────── TRUST STRIP ──────────────── -->
|
||||||
|
<section class="trust-strip">
|
||||||
|
<div class="container">
|
||||||
|
<div class="trust-row">
|
||||||
|
<span class="trust-label">Fuses signal from</span>
|
||||||
|
<ul class="trust-items">
|
||||||
|
<li>Bluetooth-LE + WiFi radio</li>
|
||||||
|
<li>DeFlock ALPR map</li>
|
||||||
|
<li>Citizen incidents</li>
|
||||||
|
<li>Waze police alerts</li>
|
||||||
|
<li>OpenStreetMap · Overpass</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── HOW IT WORKS ──────────────── -->
|
||||||
|
<section id="detect" class="section section-feature reveal">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-head">
|
||||||
|
<span class="section-tag">how it works</span>
|
||||||
|
<h2>One tap. A live threat map.</h2>
|
||||||
|
<p class="lead">
|
||||||
|
While scanning, the circle becomes an <strong>OpenStreetMap centered on
|
||||||
|
you</strong>, wrapped in a threat-color ring for the current tier and
|
||||||
|
marked with a ⌖ crosshair. Every detection is a dot, color-coded by
|
||||||
|
source — so each mark is self-explanatory. Lock the screen and the
|
||||||
|
foreground notification keeps the tier live; the phone vibrates only
|
||||||
|
when the threat <em>escalates</em>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-split">
|
||||||
|
<div class="phone-frame">
|
||||||
|
<img class="phone" src="img/overwatch-sources.png" width="856" height="2000"
|
||||||
|
alt="Detection-sources drill-down: BLE, WiFi, DeFlock, Citizen, Waze and Commercial rows, each with a color tier dot and the observations that fired it." loading="lazy">
|
||||||
|
</div>
|
||||||
|
<div class="explain-annotations">
|
||||||
|
<div class="annotation">
|
||||||
|
<span class="anno-num">1</span>
|
||||||
|
<div>
|
||||||
|
<strong>Threat-color ring</strong>
|
||||||
|
<p>The map is wrapped in the current tier's color — green, yellow,
|
||||||
|
orange, or red — so you read your situation at a glance without
|
||||||
|
opening anything.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="annotation">
|
||||||
|
<span class="anno-num">2</span>
|
||||||
|
<div>
|
||||||
|
<strong>Source-color dots</strong>
|
||||||
|
<p><span class="legend-dot red"></span> Flock / DeFlock cameras red ·
|
||||||
|
<span class="legend-dot blue"></span> Waze police blue ·
|
||||||
|
<span class="legend-dot purple"></span> Citizen incidents purple.
|
||||||
|
Tap any row for coordinates + a Maps deep-link.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="annotation">
|
||||||
|
<span class="anno-num">3</span>
|
||||||
|
<div>
|
||||||
|
<strong>Foreground notification</strong>
|
||||||
|
<p>Rebuilt on every tier change — <code>OVERWATCH • RED</code> with
|
||||||
|
the top detection's score. Priority bumps to HIGH on RED so the
|
||||||
|
system surfaces it as a heads-up, screen locked or not.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="annotation">
|
||||||
|
<span class="anno-num">4</span>
|
||||||
|
<div>
|
||||||
|
<strong>Escalation-only haptics</strong>
|
||||||
|
<p>A short pulse for YELLOW, double for ORANGE, escalating triple
|
||||||
|
for RED — but only on <em>upward</em> transitions, so it never
|
||||||
|
nags you while a threat lingers.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── DETECTION SOURCES ──────────────── -->
|
||||||
|
<section id="sources" class="section section-bento reveal">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-head">
|
||||||
|
<span class="section-tag">detection sources</span>
|
||||||
|
<h2>Six sensors, one score.</h2>
|
||||||
|
<p class="lead">Every observation is scored 0–100 by the confidence
|
||||||
|
engine; the on-screen tier is the <strong>max live score across all
|
||||||
|
sources</strong>. Cross-source corroboration — a BLE hit <em>and</em> a
|
||||||
|
DeFlock map match in the same spot — naturally pushes the tier higher
|
||||||
|
than either alone.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bento">
|
||||||
|
<article class="bento-card bento-lg">
|
||||||
|
<div class="bento-icon">📶</div>
|
||||||
|
<h3>BLE — Bluetooth-LE</h3>
|
||||||
|
<p>
|
||||||
|
Vendor MAC OUIs (Axon, Flock Penguin / Raven, XUNTONG mfg id
|
||||||
|
<code>0x09C8</code>, "TN" serial pattern), Raven service UUIDs, and
|
||||||
|
device-name patterns. Iterates <em>every</em> manufacturer-data entry
|
||||||
|
per advert — not just the first — so it doesn't miss a buried match.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card">
|
||||||
|
<div class="bento-icon">📡</div>
|
||||||
|
<h3>WiFi</h3>
|
||||||
|
<p>
|
||||||
|
BSSID OUI prefixes for Flock infrastructure (31-prefix superset) and
|
||||||
|
<code>Flock-XXXX</code> SSID patterns, polled every 35 s — just under
|
||||||
|
the Android 11+ scan throttle.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card bento-cam">
|
||||||
|
<div class="bento-icon">🎥</div>
|
||||||
|
<h3>DeFlock — ALPR map</h3>
|
||||||
|
<p>
|
||||||
|
Crowdsourced ALPR camera locations within range (200 m default) via
|
||||||
|
the Overpass API (<code>overpass.deflock.org</code> → fallback
|
||||||
|
<code>overpass-api.de</code>), with a 24 h on-disk cache and failure
|
||||||
|
backoff. Refetches when you move > 1.5 km.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card">
|
||||||
|
<div class="bento-icon">🚨</div>
|
||||||
|
<h3>Citizen</h3>
|
||||||
|
<p>
|
||||||
|
Real-time public-safety incidents — police-relevant only,
|
||||||
|
fire/medical filtered out — within range and under 30 min old,
|
||||||
|
polled every 60 s from the Citizen trending feed.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card bento-lg">
|
||||||
|
<div class="bento-icon">🚓</div>
|
||||||
|
<h3>Waze — police alerts</h3>
|
||||||
|
<p>
|
||||||
|
Live user-reported <code>POLICE</code> alerts (default 500 m,
|
||||||
|
~45 min freshness). Waze reCAPTCHA-gated its map endpoint, so
|
||||||
|
OVERWATCH reads a key-protected feed through a Caddy proxy at
|
||||||
|
<code>api.blackflagintel.com</code> that injects the key
|
||||||
|
server-side — the paid key <strong>never ships in the APK</strong>.
|
||||||
|
The app authenticates with a scoped, revocable <code>X-App-Token</code>
|
||||||
|
stored encrypted in the Android Keystore.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card">
|
||||||
|
<div class="bento-icon">🏠</div>
|
||||||
|
<h3>Commercial</h3>
|
||||||
|
<p>
|
||||||
|
Nearby consumer smart-home gear (Nest, Ring, Echo, hidden cams) and
|
||||||
|
<strong>smart glasses (Meta, Snap, Vuzix)</strong> as a secondary
|
||||||
|
situational signal, riding the BLE + WiFi scans. Score-capped at
|
||||||
|
ORANGE so a cluster of doorbells never reads as ALPR-grade certainty.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── THREAT TIERS ──────────────── -->
|
||||||
|
<section id="tiers" class="section reveal">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-head">
|
||||||
|
<span class="section-tag">confidence</span>
|
||||||
|
<h2>Four tiers. Highest live score wins.</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tiers">
|
||||||
|
<div class="tier tier-green">
|
||||||
|
<div class="tier-range">< 40</div>
|
||||||
|
<div class="tier-name">GREEN</div>
|
||||||
|
<p>Nothing credible. Idle shows a muted gray ring so "all clear" reads differently from "not scanning."</p>
|
||||||
|
</div>
|
||||||
|
<div class="tier tier-yellow">
|
||||||
|
<div class="tier-range">40 – 69</div>
|
||||||
|
<div class="tier-name">YELLOW</div>
|
||||||
|
<p>A single weak indicator. Short vibration pulse on the way up.</p>
|
||||||
|
</div>
|
||||||
|
<div class="tier tier-orange">
|
||||||
|
<div class="tier-range">70 – 84</div>
|
||||||
|
<div class="tier-name">ORANGE</div>
|
||||||
|
<p>High confidence — usually a hard radio hit or a close map match. Double pulse.</p>
|
||||||
|
</div>
|
||||||
|
<div class="tier tier-red">
|
||||||
|
<div class="tier-range">85 +</div>
|
||||||
|
<div class="tier-name">RED</div>
|
||||||
|
<p>Certain. Notification jumps to HIGH priority; escalating triple pulse.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── SCREENSHOTS ──────────────── -->
|
||||||
|
<section id="screens" class="section reveal">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-head">
|
||||||
|
<span class="section-tag">screenshots</span>
|
||||||
|
<h2>The whole app, top to bottom.</h2>
|
||||||
|
<p class="lead">No account, no onboarding, no cloud dashboard to
|
||||||
|
configure — open it, hit START, and drill into any source. Three taps
|
||||||
|
deep is as complex as it gets.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shots">
|
||||||
|
<figure class="shot">
|
||||||
|
<img class="phone" src="img/overwatch-main.png" width="1096" height="2560"
|
||||||
|
alt="OVERWATCH main screen: a live map inside a green all-clear threat ring, centered on you with a crosshair, and a STOP button." loading="lazy">
|
||||||
|
<figcaption><strong>The threat ring</strong>A live map centered on you, wrapped in the current tier's color. Green reads “all clear” at a glance.</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure class="shot">
|
||||||
|
<img class="phone" src="img/overwatch-sources.png" width="856" height="2000"
|
||||||
|
alt="Detection-sources drill-down: BLE, WiFi, DeFlock, Citizen, Waze and Commercial rows, each with a color tier dot and the observations that fired it." loading="lazy">
|
||||||
|
<figcaption><strong>Source drill-down</strong>Tap the ring for every source, its live tier, and the exact observation that fired it.</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure class="shot">
|
||||||
|
<img class="phone" src="img/overwatch-settings.png" width="856" height="2000"
|
||||||
|
alt="Settings screen: per-source toggles, proximity distance sliders, and the encrypted Waze proxy token field." loading="lazy">
|
||||||
|
<figcaption><strong>Settings</strong>Toggle each source, tune proximity distances, and paste an encrypted Waze proxy token.</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── CAPABILITIES ──────────────── -->
|
||||||
|
<section class="section section-bento reveal">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-head">
|
||||||
|
<span class="section-tag">built for the street</span>
|
||||||
|
<h2>Runs in your pocket, not the cloud.</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bento">
|
||||||
|
<article class="bento-card bento-lg">
|
||||||
|
<div class="bento-icon">🔇</div>
|
||||||
|
<h3>Passive by design</h3>
|
||||||
|
<p>
|
||||||
|
OVERWATCH only listens — it never transmits, probes, jams, or
|
||||||
|
interferes with any device or network. The Axon advertise/fuzz code
|
||||||
|
from one of the reference projects is <strong>intentionally
|
||||||
|
excluded</strong>. It reads the airwaves and public feeds; that's it.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card">
|
||||||
|
<div class="bento-icon">🫧</div>
|
||||||
|
<h3>Floating overlay</h3>
|
||||||
|
<p>
|
||||||
|
The same threat-ring map renders in a small draggable bubble over
|
||||||
|
other apps (Settings → Display over other apps), so it stays with
|
||||||
|
you while you use your phone.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card">
|
||||||
|
<div class="bento-icon">🔐</div>
|
||||||
|
<h3>Keys stay off-device</h3>
|
||||||
|
<p>
|
||||||
|
The paid Waze feed key lives server-side behind the proxy; the app
|
||||||
|
holds only a scoped, revocable token, encrypted via the Android
|
||||||
|
Keystore. No secrets in the APK.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card">
|
||||||
|
<div class="bento-icon">🩺</div>
|
||||||
|
<h3>Honest per-source health</h3>
|
||||||
|
<p>
|
||||||
|
The drill-down marks a source <code>Source unreachable</code> when
|
||||||
|
its scanner couldn't reach its feed — a silent empty result and a
|
||||||
|
real failure look different.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="bento-card">
|
||||||
|
<div class="bento-icon">🗺</div>
|
||||||
|
<h3>Your position, not your data</h3>
|
||||||
|
<p>
|
||||||
|
Location drives the proximity checks and the map on-device. No
|
||||||
|
account, no telemetry, no analytics — the scans and feeds run and
|
||||||
|
the results stay on your phone.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── CALLOUT ──────────────── -->
|
||||||
|
<section class="section section-callout reveal">
|
||||||
|
<div class="container">
|
||||||
|
<div class="callout">
|
||||||
|
<div class="callout-mark">🛡</div>
|
||||||
|
<div>
|
||||||
|
<h3>Situational awareness, not interference</h3>
|
||||||
|
<p>
|
||||||
|
OVERWATCH is a defensive, receive-only tool for knowing what's
|
||||||
|
watching <em>you</em> — Flock ALPRs, Axon body cameras, and police
|
||||||
|
presence — in the space you're already in. It does not touch, spoof,
|
||||||
|
or disrupt anything. Know your local laws; use it responsibly.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── DOWNLOAD ──────────────── -->
|
||||||
|
<section id="download" class="section section-timeline reveal">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-head">
|
||||||
|
<span class="section-tag">get it</span>
|
||||||
|
<h2>Sideload in three steps.</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-split">
|
||||||
|
<div class="phone-frame">
|
||||||
|
<img class="phone" src="img/overwatch-settings.png" width="856" height="2000"
|
||||||
|
alt="Settings screen: per-source toggles (BLE, WiFi, DeFlock, Citizen, Waze, Commercial), proximity distance sliders, and the encrypted Waze proxy token field." loading="lazy">
|
||||||
|
</div>
|
||||||
|
<div class="timeline dl-steps">
|
||||||
|
<div class="tl-col tl-shipped">
|
||||||
|
<div class="tl-tag">1 · download</div>
|
||||||
|
<ul>
|
||||||
|
<li>Grab the latest debug-signed APK from
|
||||||
|
<a href="https://github.com/KaraZajac/OVERWATCH/releases/latest">Releases</a>
|
||||||
|
(currently <strong>v0.5.3</strong>).</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tl-col tl-active">
|
||||||
|
<div class="tl-tag">2 · install</div>
|
||||||
|
<ul>
|
||||||
|
<li>Sideload it — allow "install unknown apps" for your browser or files app, then open the APK.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tl-col tl-next">
|
||||||
|
<div class="tl-tag">3 · grant + start</div>
|
||||||
|
<ul>
|
||||||
|
<li>Grant location + nearby-devices + notifications, toggle the sources you want, hit <strong>START</strong>.</li>
|
||||||
|
<li>Optional: paste a Waze proxy token in Settings for police-report coverage.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="tl-foot">
|
||||||
|
Build from source, file issues, or read the internals:
|
||||||
|
<a href="https://github.com/KaraZajac/OVERWATCH">github.com/KaraZajac/OVERWATCH</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ──────────────── FOOTER ──────────────── -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container footer-inner">
|
||||||
|
<div class="footer-col">
|
||||||
|
<div class="footer-brand">
|
||||||
|
<img class="nav-logo" src="logo.svg" width="26" height="26" alt="" aria-hidden="true">
|
||||||
|
OVERWATCH
|
||||||
|
</div>
|
||||||
|
<p class="footer-tag">
|
||||||
|
Passive Android surveillance-detection. Listens only — never
|
||||||
|
transmits.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-col">
|
||||||
|
<h4>Project</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://github.com/KaraZajac/OVERWATCH">Source</a></li>
|
||||||
|
<li><a href="https://github.com/KaraZajac/OVERWATCH/releases">Releases</a></li>
|
||||||
|
<li><a href="https://github.com/KaraZajac/OVERWATCH/releases/latest">Latest APK</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-col">
|
||||||
|
<h4>Signal from</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://deflock.me">DeFlock ALPR map</a></li>
|
||||||
|
<li><a href="https://citizen.com">Citizen</a></li>
|
||||||
|
<li><a href="https://www.waze.com">Waze</a></li>
|
||||||
|
<li><a href="https://www.openstreetmap.org">OpenStreetMap</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-col">
|
||||||
|
<h4>More</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://netslum.io/notes/overwatch-passive-counter-surveillance/">The writeup</a></li>
|
||||||
|
<li><a href="https://netslum.io">netslum.io</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container footer-bottom">
|
||||||
|
<p>
|
||||||
|
OVERWATCH is a receive-only situational-awareness tool. It does not
|
||||||
|
transmit, probe, jam, or interfere with any device or network.
|
||||||
|
</p>
|
||||||
|
<p class="footer-meta">
|
||||||
|
v0.5.3 · <a href="https://github.com/KaraZajac/OVERWATCH">github.com/KaraZajac/OVERWATCH</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="app.js" defer></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 108 108" width="108" height="108" role="img" aria-label="OVERWATCH">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="ow-bg" cx="50%" cy="40%" r="74%">
|
||||||
|
<stop offset="0" stop-color="#123a24"/>
|
||||||
|
<stop offset="1" stop-color="#0a0f12"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="4" y="4" width="100" height="100" rx="26" fill="url(#ow-bg)" stroke="#1f3a2c"/>
|
||||||
|
<path d="M54,54 L54,20 A34,34 0 0,1 82.5,36 Z" fill="#1fbf6a" fill-opacity="0.30"/>
|
||||||
|
<circle cx="54" cy="54" r="33" fill="none" stroke="#10b981" stroke-width="2.4"/>
|
||||||
|
<circle cx="54" cy="54" r="22" fill="none" stroke="#10b981" stroke-width="2.2" stroke-opacity="0.82"/>
|
||||||
|
<circle cx="54" cy="54" r="11" fill="none" stroke="#10b981" stroke-width="2" stroke-opacity="0.68"/>
|
||||||
|
<path d="M54,21 V87 M21,54 H87" stroke="#10b981" stroke-width="1.3" stroke-opacity="0.5"/>
|
||||||
|
<circle cx="70" cy="40" r="4.6" fill="#ef4444"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 932 B |
@@ -0,0 +1,35 @@
|
|||||||
|
<!doctype html><html lang="en"><head><meta charset="utf-8">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700&family=Space+Grotesk:wght@700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
width: 1200px; height: 630px; overflow: hidden; position: relative;
|
||||||
|
background: radial-gradient(130% 130% at 12% 8%, #101b2e 0%, #07070d 52%, #0b0712 100%);
|
||||||
|
color: #ecedf7; font-family: "Inter", sans-serif;
|
||||||
|
}
|
||||||
|
.wrap { padding: 74px 82px; height: 100%; display: flex; flex-direction: column; justify-content: center; }
|
||||||
|
.brand { display: flex; align-items: center; gap: 20px; margin-bottom: 26px; }
|
||||||
|
.mark { color: #10b981; font-size: 46px; filter: drop-shadow(0 0 18px rgba(16,185,129,.8)); }
|
||||||
|
.name { font-family: "Space Grotesk", sans-serif; font-weight: 700; font-size: 84px; letter-spacing: .01em; }
|
||||||
|
.tag { font-size: 31px; line-height: 1.38; color: #c5c5d3; font-weight: 500; max-width: 960px; }
|
||||||
|
.tag b { color: #ecedf7; font-weight: 700; }
|
||||||
|
.tiers { display: flex; gap: 22px; margin: 36px 0 26px; }
|
||||||
|
.tdot { display: flex; align-items: center; gap: 9px; font-family: "JetBrains Mono", monospace; font-size: 17px; color: #9a9aad; }
|
||||||
|
.tdot i { width: 15px; height: 15px; border-radius: 50%; }
|
||||||
|
.sources { font-family: "JetBrains Mono", monospace; font-size: 20px; color: #8a8a9d; }
|
||||||
|
.sources b { color: #34d399; }
|
||||||
|
.url { position: absolute; bottom: 48px; right: 82px; font-family: "JetBrains Mono", monospace; font-size: 21px; color: #5b5b75; }
|
||||||
|
</style></head><body>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="brand"><img src="logo.svg" style="width:80px;height:80px;border-radius:17px" alt=""><span class="name">OVERWATCH</span></div>
|
||||||
|
<div class="tag">Passive surveillance detection for Android. Flag nearby <b>Flock ALPRs</b>, <b>Axon body cams</b> & <b>police</b> — <b>it only listens.</b></div>
|
||||||
|
<div class="tiers">
|
||||||
|
<span class="tdot"><i style="background:#22c55e;box-shadow:0 0 12px #22c55e"></i>GREEN</span>
|
||||||
|
<span class="tdot"><i style="background:#eab308;box-shadow:0 0 12px #eab308"></i>YELLOW</span>
|
||||||
|
<span class="tdot"><i style="background:#f97316;box-shadow:0 0 12px #f97316"></i>ORANGE</span>
|
||||||
|
<span class="tdot"><i style="background:#ef4444;box-shadow:0 0 12px #ef4444"></i>RED</span>
|
||||||
|
</div>
|
||||||
|
<div class="sources">BLE · WiFi · <b>DeFlock</b> · Citizen · Waze — six sensors, fused</div>
|
||||||
|
</div>
|
||||||
|
<div class="url">overwatch.netslum.io</div>
|
||||||
|
</body></html>
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
+1123
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user