Security
Root on your host. So we show our work.
pisentry runs as root under systemd, or with hostPID and three capabilities in Kubernetes. A bug in pisentry is a bug in that host. This page says what it can touch, how it is built, and how to check a release yourself.
Threat model
Every input is attacker-controlled: log lines, file names and contents, process names and command lines from /proc. All of them pass through one sanitizer before they reach JSON, a webhook or a terminal. It strips control characters, terminal escapes, bidi overrides and invalid UTF-8, and it caps length.
- The agent opens no outbound connection except the webhook you set. The URL and token come from the environment, never the config file.
- It listens on
127.0.0.1:9817by default. In Kubernetes it listens on the pod IP behind a NetworkPolicy that admits only the monitoring namespace. - State lives in a root-only directory. Writes are atomic and fsynced.
go.modhas no require block. The dependency surface is this repository and the Go toolchain.
Root wins. An attacker who gets root can stop the agent or edit its state. The defense is to ship events off the box, to Prometheus or a webhook, and to alert on absent(pisentry_up) and on stale detectors.
What it can do on a host
As a systemd service
The unit runs as root because it reads /etc/shadow, other users' /proc entries and the journal. Then it takes almost everything away.
| Setting | Effect |
|---|---|
CapabilityBoundingSet | Only DAC_READ_SEARCH and SYS_PTRACE |
ProtectSystem=strict, ProtectHome=read-only | The OS and home directories are read-only to the agent |
NoNewPrivileges, RestrictSUIDSGID | No way to gain privileges through exec |
SystemCallFilter=@system-service | Minus debug, mount, swap, reboot, raw I/O and module calls |
RestrictAddressFamilies | IPv4, IPv6 and Unix sockets only |
MemoryDenyWriteExecute | No writable and executable memory |
MemoryMax=256M, CPUQuota=25%, Nice=10 | Stays out of the way on a 2 GB Pi |
PrivateTmp and PrivateDevices are off on purpose. The agent must see the real /tmp and /dev/shm, which is where droppers and miners live.
As a Kubernetes DaemonSet
| Grant | Why |
|---|---|
hostPID: true | See every host and pod process for miner and reverse shell detection |
Host / at /host, read-only, recursive where supported | File integrity, posture audit, SUID walk, host journalctl |
CAP_DAC_READ_SEARCH | Read /etc/shadow, journals, home directories |
CAP_SYS_PTRACE | Read /proc/<pid>/fd and /exe of other users' processes |
CAP_SYS_CHROOT | Run the host's own journalctl so the journal format always matches |
AppArmor unconfined, SELinux spc_t | Default container profiles block ptrace reads and host file reads |
/var/lib/pisentry hostPath | Per-node baselines survive pod restarts |
It is not a privileged pod. It has no service account token and a read-only root filesystem. The image is pinned by digest, because a tag can move and a digest cannot.
Supply chain
- Releases are built by GitHub Actions from version tags, never from a laptop.
- main requires signed commits, a pull request and passing checks on x86 and native arm64. Force pushes and deletion are blocked.
- The image and
SHA256SUMSare signed with cosign in keyless mode. The signing identity is the release workflow, attested by GitHub's OIDC issuer, and every signature lands in the Sigstore transparency log. There is no long-lived key to steal. v0.1.0 predates signing; verify it by digest and provenance. - Images ship SLSA provenance and an SBOM.
- Every GitHub Action is pinned to a commit SHA and every base image to a digest. Dependabot proposes updates and a human merges them.
- CI runs govulncheck, CodeQL, staticcheck, go vet and the race detector. A toolchain vulnerability fails the build.
- OpenSSF Scorecard grades the repository in public. Current score: 6.4.
Verify a release
You need cosign 3.0 or later. Signatures use the Sigstore bundle format, which cosign 2.x cannot read.
Binaries
From the release page, download SHA256SUMS, SHA256SUMS.sigstore.json and the binary for your board.
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 sha256sum --check --ignore-missing SHA256SUMS
Image
cosign verify \ --certificate-identity-regexp '^https://github.com/MrGuato/pisentry/\.github/workflows/release\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/mrguato/pisentry:<tag>
Provenance
docker buildx imagetools inspect ghcr.io/mrguato/pisentry:<tag> --format '{{json .Provenance}}'A pass means the file came from this repository's release workflow on a v* tag and has not changed since. The identity check is the part that matters. Without it, any GitHub workflow anywhere could have signed it.
Known limits
These are documented behavior, not bugs. They are here so you can decide if pisentry fits.
- Polling, not syscalls. A process that lives less than one poll, 15 seconds, can be missed.
- Port scans need firewall logging. pisentry reads kernel drop logs. It does not capture packets.
- A compromised host learns a bad baseline. Install on a clean host, or review the first baselines by hand.
- Log forgery in file mode. Any local user can write sshd-tagged lines with logger. With journald,
ssh_require_rootdrops them. - FIM hashes on an interval. A file changed and restored inside 60 seconds is not seen.
- High UDP ports. Unconnected UDP sockets on 32768 and up are skipped to avoid DNS client noise.
Report a vulnerability
Use GitHub's private vulnerability reporting. Do not open a public issue, pull request or discussion for a suspected vulnerability.
- Acknowledgement within 7 days.
- A fix or a mitigation plan for high and critical findings within 30 days of confirmation.
- Credit in the release notes and the advisory, unless you ask to stay anonymous.
In scope: crafted input that crashes, hangs or exhausts the agent; anything that makes it execute something, write outside its directories or read what it should not; suppressing or forging events beyond the known limits; escalation through the unit, the pod grants, the HTTP endpoint or the webhook sender; and release integrity. The full policy is in SECURITY.md.