Commit Graph
675 Commits
Author SHA1 Message Date
Mauricio CamayoandClaude Sonnet 5 d0a3a5416b Add WIFICANARY plugin - passive WiFi rogue-AP detection
Periodic iw-scan-based detection of the 6 heuristics that don't need
monitor-mode hardware (see issue #1789): pwnagotchi/Pineapple signatures,
evil-twin/open clones, baseline-AP-absent-with-clone, security downgrades,
and duplicate-SSID/different-vendor - all evaluated against a user-curated
trusted-AP baseline (WIFICANARY_trusted_aps). A detection creates a
flagged Devices entry even for BSSIDs that never associate, per the
addendum on the same issue.

- WIFICANARY_TRUSTED_SECURITY is multi-select: an observed encryption
  exactly matching any selected value is accepted; otherwise it's flagged
  if weaker than the strongest selected value (deliberate - comparing
  against the weakest would make multi-select pointless, since anything
  at/above the weakest would silently pass regardless of the rest of the
  selection).
- Added a "known device turned rogue" motor: escalate_known_devices()
  cross-references each detection's BSSID against the Devices table via
  the new DeviceInstance.getAllByMacs(). This covers the BSSID-identity
  half of the issue #1789 addendum's motor 10; the deauth/probe-source-MAC
  half still needs monitor-mode data this plugin doesn't have.
- Vendor is deliberately not looked up by this plugin - any device it
  creates gets devVendor filled in for free by core's own vendor_update
  plugin on its next pass.

43 wificanary unit tests + 10 DeviceInstance.getAllByMacs() tests, all
test_plugin_conventions.py checks pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
2026-09-23 15:56:47 -05:00
Mauricio CamayoandClaude Sonnet 5 b175a3b65f Address jokob-sk review: shorten UI description, map scanSourcePlugin
Settings-UI description trimmed to one short line - implementation
detail (Socket Proxy, column mapping, CREATE_DEV behavior) already
lives in README, doesn't belong in the Settings page string.

scanSourcePlugin now maps to a static "DOCKERDISC" value (same
Dummy-column pattern arp_scan already uses), so a container device
created by this plugin gets devSourcePlugin set correctly instead of
NULL - every other CurrentScan-mapped plugin already does this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
2026-09-20 16:22:12 -05:00
Mauricio CamayoandClaude Sonnet 5 dea6a553fb Address CodeRabbit review: add DOCKERDISC_IMPORT_ON, docstrings
Declares DOCKERDISC_IMPORT_ON (default on) so an operator can fully opt
this plugin out of CurrentScan promotion. Needed because
DOCKERDISC_CREATE_DEV alone doesn't cover it: a macvlan/ipvlan
container's row always carries a real scanMac, so even with
CREATE_DEV off, an already-existing device for that MAC (found
independently by ARP/Nmap) still gets its presence/devLastIP/
devParentMAC updated by this plugin on every run - only IMPORT_ON can
turn that off. The two settings are independent, per jokob-sk's PR
feedback - IMPORT_ON gates promotion for the whole run, CREATE_DEV
gates device creation per row.

Also adds missing docstrings to process_host()/main() (CodeRabbit
docstring-coverage check), matching the style already used elsewhere
in this file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
2026-09-18 20:05:37 -05:00
Mauricio CamayoandClaude Sonnet 5 cfde00048a DOCKERDISC v2: optional device creation for LAN-visible containers
Maps DOCKERDISC to CurrentScan (scanMac/scanCreatesDevice/scanParentMAC/
scanLastIP) so a container on a macvlan/ipvlan network can opt into
creating or confirming its own device, parented to its Docker host.
Gated by a new DOCKERDISC_CREATE_DEV setting (default off). A container
without its own MAC (bridge/overlay/etc.) never creates a device either
way - the framework's blank-scanMac guard blocks the whole group
regardless of the setting.

Reuses the existing objectPrimaryId/extra column definitions (already
host MAC / container IP) to also feed scanParentMAC/scanLastIP, so every
promoted container is auto-parented to its host with no extra plugin
logic. Two new hidden columns (helpVal1/helpVal2) carry the per-container
scanMac/scanCreatesDevice values.

Tests: 33 -> 35, both DOCKERDISC_CREATE_DEV on/off paths asserted.
Live-verified end to end against a real built image (docker-socket-proxy
+ isolated macvlan/bridge test containers), since IMPORT_ON isn't in any
released NetAlertX image yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
2026-09-18 17:14:10 -05:00
jokob-sk 8dcd670d24 DOCS+BE: skill updates, write_notifications fix #1793 2026-09-18 08:42:42 +10:00
jokob-sk 3bf10f8378 DOCS+BE: skill updates, write_notifications fix #1793 2026-09-18 08:20:31 +10:00
jokob-sk 52d1698221 DOCS+BE: skill updates, write_notifications fix #1793 2026-09-18 08:09:22 +10:00
Jokob @NetAlertX 014b960159 Merge pull request #1791 from agueybanapr/patch-1
Refactor config import logic for legacy column references
2026-09-16 08:12:41 +10:00
Mauricio CamayoandClaude Sonnet 5 091e648e88 Fix DOCKERDISC_HOST_MAC docs and strengthen case-insensitivity test
resolve_host_mac() returns the manually configured MAC immediately,
with no Socket Proxy /info call at all - the config.json text still
described it as a fallback used only when auto-detection fails.
Reworded both the setting's own description and the parent "Docker
hosts" description to match actual behavior.

The case-insensitivity regression test for lookup_device_mac() stubbed
DeviceInstance.getByMac() to return a fixed row regardless of input,
so it passed even without exercising real collation - functionally a
duplicate of test_lookup_device_mac_found. Replaced it with a
delegation check, and added real SQLite-backed coverage for
DeviceInstance.getByMac()'s case-insensitivity in
test/backend/test_device_instance.py. That surfaced a gap in the
shared db_test_helpers.py fixture: its Devices.devMac column was
missing the COLLATE NOCASE that the real schema declares, so it could
not have exercised this behavior. Fixed the fixture to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
2026-09-15 12:57:04 -05:00
Mauricio Camayo b0d1776221 Trim module docstring: drop design-history attribution and verification date
Per jokob-sk's review - unnecessary details belongs in the PR/commit
history, not the docstring (matches CLAUDE.md's own convention: a
docstring describes current behavior, not a changelog of why).
2026-09-15 12:06:04 -05:00
Mauricio Camayo 776b462001 Merge remote-tracking branch 'upstream/main' into add-dockerdisc-plugin 2026-09-15 11:58:18 -05:00
Mauricio Camayo 3b83d2403b Address jokob-sk review: DeviceInstance instead of raw SQL, drop HTML entity/partial translations/dead spec-file reference
- resolve_host_mac()/lookup_device_mac() now use the new
  DeviceInstance.getAllByName()/getByMac() core methods instead of
  querying Devices directly - no more direct SQL access from the plugin.
- config.json: removed the &rarr; HTML entity from a description (plain
  ASCII ->, matching e.g. pihole_monitor's convention), and dropped the
  partial es_es/de_de translations scattered through settings/columns
  (English only now, matching e.g. rest_import) instead of leaving some
  strings translated and others not.
- script.py: removed the two remaining references to
  PLUGIN_DOCKERDISC_SPEC.md, a file that was never included in this PR.
2026-09-15 11:58:10 -05:00
Pedro Berdasco fbc9fcee7e Refactor config import logic for legacy column references
Ensure legacy column references are renamed only if the config file has changed.  Fix unnecessary config scan in importConfigs, was causing the container to run at over 80% CPU.  

QA tested locally, all test passed!
2026-09-15 08:33:48 +00:00
jokob-sk e237b92657 BE+FE: plugin view fixes, skills, new core method and tests 2026-09-15 08:46:44 +10:00
Mauricio Camayo 6a26804a5e Address CodeRabbit review: request timeout budget, shape validation, ambiguous devName, README fix
- DockerHost now takes a shared run deadline instead of a per-request
  timeout duration - every _get() call is capped by whatever's left of
  that budget (and REQUEST_TIMEOUT_DEFAULT as an upper bound), so one
  slow/hanging host can't burn the whole RUN_TIMEOUT and starve every
  other configured host. config.json's hosts param now also sets
  timeoutMultiplier, scaling the outer kill-timeout by host count.
- _get() validates the parsed response's shape (dict for /info, list for
  /containers/json and /networks) before returning it, rejecting a
  malformed/unexpected payload the same as a network failure instead of
  letting a caller crash on it further down.
- resolve_host_mac()'s hostname match now detects more than one device
  sharing that name and treats it as ambiguous (falls back to manual),
  instead of silently picking an arbitrary one via LIMIT 1.
- README: the Socket Proxy is only reachable at 127.0.0.1:2375 under the
  network_mode: host case described above it, not under normal compose
  networking - fixed the doc to not imply either URL works there.
2026-09-14 10:01:40 -05:00
Mauricio Camayo 94a5cd4968 Add DOCKERDISC plugin: enrich existing devices with their Docker containers
Read-only enrichment plugin, not an import/discovery plugin. For each
configured Docker host (via Docker Socket Proxy, never /var/run/docker.sock
directly), lists that host's containers under the host device's own
Device Details -> Plugins -> DOCKERDISC tab.

- Never creates a device, for either a host or a container - matches
  against hosts already discovered the normal way (ARP/Nmap).
- Every container is listed (bridge/overlay included), not only
  macvlan/ipvlan ones - a container only gets its own MAC/IP shown when
  it has a macvlan/ipvlan network.
- Host MAC auto-detected via the Socket Proxy's /info -> Devices.devName
  match, with a manual fallback.
2026-09-14 09:11:34 -05:00
jokob-sk 6ab220fd8c BE+FE: async event execution 2026-09-14 18:17:06 +10:00
jokob-sk 7277a07660 BE: review fixes 2026-09-14 16:51:35 +10:00
jokob-sk 321824db8e BE: events, device pagination for performance 2026-09-14 14:36:27 +10:00
jokob-sk dc7274a2e9 DOCS: skill cleanup, lower case mac fixes, trigger performance 2026-09-14 14:26:14 +10:00
jokob-sk 1e2ac94496 FE+BE: skill cleanup, review fixes 2026-09-14 09:30:51 +10:00
jokob-sk f7fef5d196 FE+BE: plugins config fixes #1784 2026-09-14 08:49:15 +10:00
jokob-sk f52de6cbdc FE+BE: performance improvements 2026-09-14 08:29:02 +10:00
jokob-sk 37bf86a805 BE: conditional device import support, notification supression support during scan #1721 2026-09-11 08:26:16 +10:00
jokob-sk 6dab348de2 BE: conditional device import support, notification supression support during scan #1721 2026-09-11 08:01:41 +10:00
jokob-sk dd4fc057c8 BE: SQL refactor presence 2026-09-10 22:11:46 +10:00
jokob-sk d9947a2829 BE: conditional device import support, notification supression support during scan #1721 2026-09-10 21:00:50 +10:00
jokob-sk de730e85e9 BE: conditional device import support, notification supression support during scan #1721 2026-09-10 18:41:07 +10:00
jokob-sk 34993ed77b BE: conditional device import support, notification supression support during scan #1721 2026-09-10 17:40:31 +10:00
jokob-sk 44c52a3cf4 BE: conditional device import support, notification supression support during scan #1721 2026-09-10 14:27:44 +10:00
jokob-sk c3bdd92b8d BE: conditional device import support, notification supression support during scan #1721 2026-09-10 13:51:09 +10:00
jokob-sk cc3ccbcaf0 DOCS: skills + SQL cleanup 2026-09-10 10:59:33 +10:00
jokob-sk c6164798e9 DOCS: plugins 2026-09-10 09:49:40 +10:00
jokob-sk b5d62b73c9 FE: Easier icon add in Settings #1773 2026-09-09 08:08:31 +10:00
jokob-sk ba0077b99d DOCS: plugins 2026-09-07 09:44:37 +10:00
jokob-sk 85325dad93 DOCS: plugins 2026-09-07 09:30:55 +10:00
jokob-sk 18092b3179 DOCS: plugins 2026-09-07 08:24:53 +10:00
jokob-sk d478deddc9 FE+DOCS: custom props icon select fix + docs cleanup 2026-09-06 11:13:21 +10:00
jokob-sk 4d0175c660 BE+FE+DOCS: run plugin from custom props/actions 2026-09-05 10:54:56 +10:00
jokob-sk 939e6494ba BE+PLG: mac case sensitivity causing double detection #1775 2026-09-04 08:18:02 +10:00
jokob-sk 4be05c958a BE+PLG: mac case sensitivity causing double detection #1775 2026-09-04 08:08:22 +10:00
jokob-sk 5751811243 plugin template update 2026-09-02 07:58:31 +10:00
Jokob @NetAlertX df0ef6ec17 Merge pull request #1765 from mauricio-camayo/add-pihole-monitor-plugin
Add pihole_monitor plugin: combined Pi-hole device import + query anomaly detection
2026-09-01 08:31:13 +10:00
Jokob @NetAlertX 72871e23b1 Merge pull request #1768 from netalertx/next_release
PLG+DOCS: plugins dev docs for temp files + adguard export cleanup
2026-09-01 07:47:07 +10:00
Mauricio Camayo d6b4696ac9 fix: track per-source delta so one instance's reset can't mask the other's spike
Addresses CodeRabbit review on PR #1765 (pullrequestreview-5069337680).

pihole_monitor.py:
- last_raw is now tracked per source ({"primary": N, "secondary": M}
  per device) instead of one combined value. Combining raw totals
  across sources before diffing let a counter reset on one instance
  silently net out against real traffic on the other - e.g. primary
  +2000 (a real spike) and secondary resetting 1000->5 (-995) would
  combine into a raw delta of only 1005, hiding most of the primary's
  actual spike behind the secondary's unrelated restart.
- New aggregate_source_deltas(): diffs each source independently via
  compute_delta(), then sums only the valid deltas. A source with no
  valid delta this run (bootstrapping or just reset) contributes
  nothing and doesn't block the others; each source keeps its own
  reference point going forward.
- State loaded from before this change (last_raw as a plain number,
  not per-source) is now tolerated instead of crashing - treated as no
  prior reference point, so every source just bootstraps fresh on the
  next run.

README.md:
- Fixed a self-contradicting line: a less frequent schedule means
  larger per-run deltas, so PIHOLEMON_MIN_BLOCKED may need *raising*,
  not lowering as it previously said.
- Corrected PIHOLEMON_HISTORY_DAYS guidance: it's a retention window,
  not a detection delay. A new device becomes evaluable on its 3rd
  successful run (1st anchors the counter, 2nd records the first
  delta, 3rd has a baseline to compare against), not after the full
  retention window.

Tests: 54 (up from 48). New coverage: aggregate_source_deltas() unit
tests including the exact dual-source reset-masking scenario, a
main()-level integration test for the same, and a regression test for
tolerating pre-per-source state. Both the reset-masking fix and the
legacy-state guard verified via mutation testing (reverted each,
confirmed the relevant tests fail, restored). 99% line+branch coverage
maintained.
2026-08-31 12:51:46 -05:00
Mauricio Camayo e543f14d08 fix: address round 2 of jokob-sk's maintainer review
References PR #1765.

Docs:
- Added PIHOLEMON to docs/PLUGINS.md and a new "Approach 4" section in
  docs/PIHOLE_GUIDE.md, leading with anomaly detection (the actual
  differentiator vs PIHOLEAPI) and explaining when to pick each plugin.
- README/PLUGINS.md/config.json's UI-facing description all reordered
  and shortened to lead with anomaly detection instead of device
  import, and to drop implementation detail that belongs in the
  README, not the Settings page.
- Trimmed the "Why not extend PIHOLEAPI" README section per feedback -
  useful context for a maintainer, not for an end user configuring
  the plugin.

config.json / pihole_monitor.py:
- RUN defaults to "disabled", matching every other non-core plugin.
- VERIFY_SSL split into PRIMARY_VERIFY_SSL / SECONDARY_VERIFY_SSL -
  each instance can be http/https independently. Settings reordered so
  each *_VERIFY_SSL sits right under its matching *_PASSWORD.
- GRAPHQL_TOKEN removed; graphql_token now reads the core API_TOKEN
  setting instead of a plugin-specific duplicate.
- GRAPHQL_URL replaced with a GET_OWNER boolean - the endpoint is now
  derived from this app's own GRAPHQL_PORT (single source of truth)
  instead of a URL the user had to keep in sync by hand.
- HISTORY_LENGTH (run count) replaced with HISTORY_DAYS (a real time
  window): state now stores [timestamp, delta] samples and
  trim_history() drops anything older than the window, so the
  baseline means the same thing regardless of schedule - a faster
  schedule adds more data points instead of shrinking the window.
- STATE_FILE moved from the log folder to dbFolderPath, so the rolling
  anomaly baseline survives NetAlertX upgrades instead of being wiped
  with the logs.
- netalertx_device_owner() (1 GraphQL call per device) replaced by
  netalertx_device_owners() (1 call per run, batched) - avoids N
  blocking round-trips on a large network.
- Fixed a zero-baseline bug: `bool(... and baseline and ...)` silently
  exempted a device with an all-zero blocked-query history (0.0 is
  falsy in Python) from ever being flagged, even on its first real
  spike. Now checks `baseline is not None`.
- Fixed the placeholder-MAC filter: only excluded the literal "ip-::",
  not Pi-hole's general "ip-<address>" placeholder pattern. Caught
  downstream by is_mac() either way, but now the actual placeholder
  check does what it looks like it does.
- Fixed a cumulative-counter bug: Pi-hole's /api/stats/top_clients
  returns a count that's cumulative since FTL last started, not a
  per-interval or daily-resetting one (confirmed against FTL's own
  source and long-standing user reports that it doesn't reset at
  midnight). Comparing that raw total directly against a rolling
  average made any device's ordinary growing traffic look like an
  escalating anomaly. compute_delta() now diffs each run's raw count
  against the previous run's (state gained a per-key last_raw
  reference point alongside the delta history) - None (not 0) on the
  first-ever run for a device or right after a counter reset, so
  those runs re-anchor the reference point instead of fabricating or
  swallowing a delta.
- RUN_SCHD default changed from every 6 hours to every 5 minutes now
  that the baseline window is real days, not run count, so a frequent
  schedule only adds data points instead of narrowing the window; also
  matches the default most other device-scanner plugins use.
- RUN_SCHD gained the same live cron-validity checkmark ARPSCAN and
  other scanner plugins use (a ✓/✗ icon next to the field, validated
  client-side against a regex) - reuses the existing generic
  validateRegex() widget, nothing plugin-specific to build.

Tests: 48 tests (up from 37), 99% line+branch coverage. Every fix
above verified via mutation testing (deliberately broken, confirmed
the relevant test fails, then restored).
2026-08-31 11:49:02 -05:00
jokob-sk d155633164 better scaffolding, robustness 2026-08-31 13:45:30 +10:00
jokob-sk 81202afa31 PLG+DOCS: better scaffolding 2026-08-31 11:27:25 +10:00
jokob-sk e44b17faa7 PLG+DOCS: better scaffolding 2026-08-31 10:01:09 +10:00
jokob-sk 72729d8624 PLG+DOCS: plugins dev docs for temp files + adguard export cleanup 2026-08-31 08:50:30 +10:00