Host security for Raspberry Pi

Know what is hitting your Pi.

pisentry watches SSH, processes, files and listeners on Raspberry Pi and ARM Linux. It ranks the ten attacks that matter and tells you where to look first.

  • One static binary
  • No eBPF or kernel modules
  • Standard library only
  • MIT license
A single board computer with pisentry running on its processor GPIO WLAN RAM PISENTRY NODE ARM64 LINUX PWR ACT
pisentry report
$ sudo pisentry report
pisentry Top 10  |  host kubepi  |  last 24h  |  2026-10-15 14:02 EDT

#   ID    CATEGORY                 EVENTS  WORST     LAST SEEN  ALL TIME  MITRE
1   PS06  Cryptominer              2       critical  35s ago    2         T1496
2   PS07  Unexpected listener      2       critical  38s ago    2         T1571
3   PS02  Suspicious login         1       critical  40s ago    1         T1078
4   PS08  Reverse shell            1       critical  39s ago    1         T1059.004
5   PS01  SSH brute force          1       high      40s ago    1         T1110
6   PS04  Privilege escalation     1       high      40s ago    1         T1548.001,T1548.003
7   PS09  Port scan                1       high      40s ago    1         T1046
8   PS03  Insecure posture         1       medium    1m ago     1         T1078.001,T1021.004
9   PS05  Persistence              0       clean     -          0         T1053.003,T1543.002,...
10  PS10  Critical file tampering  0       clean     -          0         T1574.006,T1556.003,...

Real output from a run against the bundled attack simulation. Every category shows, so the report reads as a status board on a quiet day too.

0
third-party Go modules
7.5MB
static arm64 binary
16MiB
memory on a 2 GB Pi 4 in k3s
10
attack categories, mapped to MITRE ATT&CK
6.4
OpenSSF Scorecard, checked in public

Why pisentry

Small boards get attacked too.

A Pi with SSH open sees brute force within hours. Most security agents were built for big servers. This one was built on a Pi, for the Pi.

No eBPF needed

Most modern eBPF agents need kernel BTF. Stock Raspberry Pi OS kernels do not ship it. pisentry reads logs, /proc and files, so it runs on the kernel you already have.

Small enough to read

6,357 lines of Go and no third-party modules. The whole dependency surface is this repo and the Go toolchain. You can audit it in an afternoon.

Answers, not noise

A ranked Top 10 shows what matters this week. A cooldown turns one brute-force campaign into one alert, not five hundred.

The Top 10

Ten attacks. Ranked.

Each category maps to MITRE ATT&CK and ranks by severity inside the window. Critical counts ten, high five, medium two, low one.

PS01

SSH brute force

Five failed logins from one IP in five minutes. Twenty is critical.

auth logT1110
PS02

Suspicious login

A success after failures, a direct root login, or a first login from a new source.

auth logT1078
PS03

Insecure posture

Password SSH, root login, empty passwords, the pi user with a password, telnet, no auto updates.

auditT1078.001T1021.004
PS04

Privilege escalation

A new SUID binary, a sudoers change, a user added to sudo or docker, risky sudo use.

FIMSUID walkT1548
PS05

Persistence

New cron jobs, systemd units, authorized_keys lines, shell profile and udev changes.

FIMT1053.003T1098.004
PS06

Cryptominer

Known miner names, stratum pool arguments, hot processes in /tmp or from a deleted binary.

/procT1496
PS07

Unexpected listener

A port that was not open during learning. Known backdoor ports are critical.

/proc netT1571
PS08

Reverse shell

A shell or interpreter with stdin, stdout or stderr wired to a TCP socket.

/proc fdT1059.004
PS09

Port scan

One source hits twelve or more ports in sixty seconds in the firewall drop log.

kernel logT1046
PS10

File tampering

passwd, shadow, PAM, sshd_config, ld.so.preload, or a new UID 0 account.

FIMT1574.006T1556.003

Next on the roadmap: PS11 fileless execution, PS12 k3s and container escape, and multi-stage attack chains. See the roadmap.

How it works

From log line to alert in one process.

No manager, no database, no agent fleet server. Seven detectors feed one engine, and the engine feeds the tools you already run.

Signals

  • journald or auth.log
  • kernel drop log
  • /proc every 15s
  • /proc/1/net every 30s
  • /etc, cron, systemd every 60s
  • SUID walk every 6h
  • posture audit every 6h

Detectors

  • auth
  • portscan
  • proc
  • listeners
  • fim
  • suid
  • audit

Engine

  • sanitize every string
  • dedupe, 15m cooldown
  • tag with MITRE IDs
  • rank the Top 10

Outputs

  • /metrics :9817
  • JSON events
  • ntfy, Discord, Slack
  • any webhook
  • /api/top10
  • state.json

Learns first

For ten minutes it records listeners and login sources without alerting.

Alerts once

A changed file alerts once, then the new hash becomes the baseline.

Knows apt

If a package manager is running, file change severity drops one level.

Trusts no input

Every name, path and command line passes one sanitizer before output.

Keeps a budget

Thirty alerts an hour, and critical alerts get their own budget.

See it

Fits the stack you run.

Prometheus metrics, a Grafana dashboard, one JSON event per line, and push alerts. Nothing new to host.

The bundled dashboard: severity totals, the ranked Top 10, events over time, failing posture checks, SSH failures per node and detector freshness.

The pisentry Grafana dashboard showing the ranked Top 10 and events over time

One object per line on stdout, so journald, kubectl logs and any log shipper can read it.

{"time":"2026-10-15T05:14:02Z","host":"kubepi","category":"PS08",
 "category_name":"Reverse shell","severity":"critical","mitre":["T1059.004"],
 "detector":"proc",
 "summary":"Reverse shell: bash (pid 5358) has fd 0 connected to 192.0.2.2:4445",
 "fields":{"pid":"5358","ppid":"1","comm":"bash","exe":"/usr/bin/bash",
           "remote":"192.0.2.2:4445","fd":"0","uid":"0"}}

Event counters start at zero, so increase() sees the first event. Alert on the agent going quiet, not just on attacks.

# TYPE pisentry_events_total counter
pisentry_events_total{category="PS08",name="Reverse shell",severity="critical"} 1
pisentry_events_total{category="PS01",name="SSH brute force",severity="high"} 1
pisentry_unexpected_listeners 2
pisentry_detector_last_run_timestamp_seconds{detector="proc"} 1.7605e+09
pisentry_up{host="kubepi"} 1

# alert rules worth having
increase(pisentry_events_total{severity="critical"}[5m]) > 0
absent(pisentry_up)
time() - pisentry_detector_last_run_timestamp_seconds{detector=~"proc|listeners|fim"} > 900

High and critical events go to ntfy, Discord, Slack or any webhook. The URL and token come from the environment, never from the config file.

# /etc/pisentry/config.json
"alerts": { "type": "ntfy", "min_severity": "high", "max_per_hour": 30 }

# /etc/pisentry/pisentry.env  (mode 0600)
PISENTRY_WEBHOOK_URL=https://ntfy.sh/a-long-random-topic-name

$ sudo systemctl restart pisentry && sudo pisentry test-alert

Built for the Pi

Light enough for a 2 GB board.

It runs on three Pi 4 nodes and one x86 box in a k3s cluster every day. The numbers below come from that cluster.

arm64pisentry-linux-arm64

Pi 5, Pi 4, Pi 400, Pi 3, Zero 2 W tested on Pi 4

64-bit Raspberry Pi OS, Ubuntu or Debian. Also proven in CI on native arm64 runners.

armv7pisentry-linux-armv7

Pi 2, Pi 3, Zero 2 W on 32-bit OS builds

Same code, cross-compiled. Hardware test reports are welcome.

amd64pisentry-linux-amd64

x86 nodes in the same cluster tested on RHEL 10

Works with SELinux enforcing. One multi-arch image covers the whole cluster.

armv6not built yet

Pi 1 and the first Pi Zero planned

On the roadmap with ARM benchmarks.

Memory per node

Measured with kubectl top on 2026-10-16. The hard cap is 256 MiB under systemd and in the pod. The process stays small; the cap leaves room for kernel file cache, which counts against it.

Pi 4, 8 GBcontrol plane
30 MiB
Pi 4, 4 GBworker
10 MiB
Pi 4, 2 GBworker
16 MiB
x86, RHEL 10worker
11 MiB

CPU was 1 to 3 millicores per node. That is under one third of one percent of a core.

Kind to SD cards

State is written only when it changed, at most every 30 seconds.

Safe on power loss

Writes are atomic and fsynced. A power cut leaves the old file or the new one.

Compared

Runs next to Falco, not instead of it.

Falco is great at the container runtime. pisentry covers the host underneath: auth abuse, posture, files and persistence.

Falcopisentry
SignalSyscalls via eBPF or a kernel moduleLogs, /proc, file hashes
Best atContainer runtime behaviorSSH and auth abuse, posture, file integrity, host persistence
Stock Pi kernelNo BTF, so a kernel module must be built per nodeRuns as is
NeedsPrivileged pod, kernel driverThree capabilities, no kernel features
Memory request128 Mi24 Mi
Sees a 1 ms processYesNo, it polls every 15 s

Get started

Audit a Pi in four commands.

No install and no daemon. The audit exits with code 2 if anything is high or critical, so it also works in scripts.

curl -fsSLO https://github.com/MrGuato/pisentry/releases/latest/download/pisentry-linux-arm64
curl -fsSLO https://github.com/MrGuato/pisentry/releases/latest/download/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
chmod +x pisentry-linux-arm64 && sudo ./pisentry-linux-arm64 audit

Use pisentry-linux-armv7 on 32-bit Pi OS and pisentry-linux-amd64 on x86.

FAIL    high      SSH-02  SSH root login permitted       PermitRootLogin yes
FAIL    medium    SSH-01  SSH password authentication enabled
How to fix:
  SSH-02: Set 'PermitRootLogin no' (or prohibit-password if you need key-based root).
git clone https://github.com/MrGuato/pisentry && cd pisentry
sudo ./deploy/install.sh

The binary goes to /usr/local/bin, config to /etc/pisentry and secrets to a 0600 env file. The unit keeps two capabilities, a syscall filter, a read-only system and a 256 MiB memory cap.

Copy deploy/kubernetes/ into your cluster repo. The DaemonSet pins the image by digest, because this agent is root-equivalent on every node.

kubectl -n pisentry get ds pisentry
kubectl -n pisentry exec ds/pisentry -- pisentry report
kubectl -n pisentry logs -l app.kubernetes.io/name=pisentry -f | jq 'select(.severity=="critical")'

Releases after v0.1.0 are signed by the release workflow with Sigstore cosign. You need cosign 3 or later.

cosign verify-blob \
  --bundle SHA256SUMS.sigstore.json \
  --certificate-identity-regexp '^https://github.com/MrGuato/pisentry/\.github/workflows/release\.yml@refs/tags/v' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  SHA256SUMS

More ways to verify

Trust

Security you can check.

A host agent runs as root. So every step from commit to container is locked down, and you can check each one yourself.

Signed commits

main requires signed commits, a pull request and green checks. No force pushes, no bypass.

Signed releases

Releases after v0.1.0 sign the image and checksums keyless with cosign. The identity is the release workflow.

Provenance and SBOM

Every image ships SLSA provenance and an SBOM you can inspect with buildx.

Pinned, all of it

Actions by commit SHA, base images by digest. Dependabot proposes, a human merges.

Scanned on every push

govulncheck, CodeQL, staticcheck and go vet. Tests run on x86 with the race detector and on native arm64.

Scorecard 6.4

OpenSSF Scorecard grades the repo's practices in public.

Read the security page for the threat model, pod grants and how to report a bug in private.

FAQ

Straight answers.

Does it replace Falco?

No. Falco watches syscalls from the container runtime. pisentry watches the host: logs, files, processes and posture. Run both where you can. On a stock Pi kernel, pisentry may be the one that runs.

Will it slow down my Pi?

On a 2 GB Pi 4 in k3s it uses 16 MiB of memory and 2 millicores of CPU. It is capped at 256 MiB.

One known case where it did: v0.1.0 had a 96 MiB cap and re-ran its SUID walk on every start. On a control-plane Pi with a large /home, the kernel cache from that walk filled the cap, the agent restarted in a loop, and its reads competed with k3s on the SD card. v0.1.1 raised the cap and resumes slow scans after a restart instead.

Why does it run as root?

It reads /etc/shadow, other users' /proc entries and the journal. The systemd unit keeps two capabilities and the pod keeps three. Everything else is taken away.

What if an attacker gets root?

Root wins. An attacker with root can stop the agent or edit its state. Ship events off the box and alert when pisentry_up goes missing. That is the real defense.

Does it block attacks?

Not today. pisentry detects and alerts. Automated response is on the roadmap, off by default and fenced by allowlists.

Can I write my own rules?

Thresholds, allowlists and extra watch paths are config today. A rule engine for your own patterns is the next milestone.

What does it cost?

Nothing. It is MIT licensed, and it stays that way.

Check your Pi in one minute.

Download, verify, run. If the audit finds nothing, you sleep better. If it does, it tells you how to fix it.

curl -fsSLO https://github.com/MrGuato/pisentry/releases/latest/download/pisentry-linux-arm64
curl -fsSLO https://github.com/MrGuato/pisentry/releases/latest/download/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
chmod +x pisentry-linux-arm64 && sudo ./pisentry-linux-arm64 audit