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:9817 by 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.mod has 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.

SettingEffect
CapabilityBoundingSetOnly DAC_READ_SEARCH and SYS_PTRACE
ProtectSystem=strict, ProtectHome=read-onlyThe OS and home directories are read-only to the agent
NoNewPrivileges, RestrictSUIDSGIDNo way to gain privileges through exec
SystemCallFilter=@system-serviceMinus debug, mount, swap, reboot, raw I/O and module calls
RestrictAddressFamiliesIPv4, IPv6 and Unix sockets only
MemoryDenyWriteExecuteNo writable and executable memory
MemoryMax=256M, CPUQuota=25%, Nice=10Stays 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

GrantWhy
hostPID: trueSee every host and pod process for miner and reverse shell detection
Host / at /host, read-only, recursive where supportedFile integrity, posture audit, SUID walk, host journalctl
CAP_DAC_READ_SEARCHRead /etc/shadow, journals, home directories
CAP_SYS_PTRACERead /proc/<pid>/fd and /exe of other users' processes
CAP_SYS_CHROOTRun the host's own journalctl so the journal format always matches
AppArmor unconfined, SELinux spc_tDefault container profiles block ptrace reads and host file reads
/var/lib/pisentry hostPathPer-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 SHA256SUMS are 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_root drops 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.

Report in private

  • 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.