Opinionated Falco rules and supporting configuration for a Linux developer workstation. Encodes ~3 months of false-positive tuning so a fresh Falco install is actually usable on a desktop instead of drowning you in alerts on day one.
Targets Lubuntu / XFCE / i3 with libvirt VMs, VS Code, Claude Code, and Google Chrome. Other desktop environments will need edits.
- Custom CRITICAL detection for any process under the user's Claude Code
install (including Bun worker threads and the VS Code extension subprocess)
reading
~/.ssh/. - Custom WARNING detection for unauthorized reads of Claude Code's credentials, config, or settings files.
- Targeted exceptions for the noisy upstream rules on a real workstation:
Write below rootfor virtiofsd / vring_worker (libvirt virtio-fs)Write below etcfor Chrome's daily cron rewriting/etc/default/google-chromeUpdate Package Repositoryfor Chrome's daily cron updatingsources.list.dRead sensitive file untrustedfor i3lock, xfce4-screensaver, lightdm, pkexec, systemd-executor PAM accessRead ssh informationfor gnome-keyring-daemon andsystemd-tmpfiles --cleanSet Setuid or Setgid bitfor Chrome, VS Code, and dpkg postinst chmodModify binary dirsfor VS Code package upgrades removing/usr/bin/codeLaunch Suspicious Network Tool on Hostfor interactivedigandtcpdumpUnexpected UDP Trafficfor Chrome, VS Code, opencode, Claude Code, dev tooling, fwupd, and APT- …and more (see comments in the rules template).
- Exceptions are scoped by
proc.exepathwhere possible to avoid trusting a renamed attacker binary that happens to share a process name. - Falcosidekick → Discord forwarding for warning+ priority alerts.
- Falco webserver locked to
127.0.0.1. logrotateconfig (weekly, 12 rotations, compressed) for/var/log/falco/alerts.json.
- Not for containers or Kubernetes — workstation only.
- Not for headless servers — assumes an X session, screen locker, GUI auth.
- Not for GNOME/Wayland — written for XFCE/i3 specifically.
- No per-file SPDX headers, no contributor model — internal use, not soliciting PRs.
.
├── LICENSE Apache 2.0
├── NOTICE attribution
├── README.md this file
├── .gitignore
└── falco/
├── .env.example copy to .env, fill in
├── apply-rules.sh renders + validates + deploys
├── rules.d/
│ ├── 00-trust.yaml.template trustworthy_exe macro
│ ├── 10-macro-overrides.yaml.template upstream macro extensions
│ ├── 20-claude-detection.yaml.template custom Claude rules
│ └── 30-rule-exceptions.yaml.template upstream rule exceptions
├── falcosidekick.yaml.template sidekick config (no secrets)
├── falcosidekick.service.d-override.conf systemd drop-in for secrets
├── falco.workstation.yaml output field overrides
├── falco-alerts.logrotate log rotation
├── falco-healthcheck.sh periodic health check
├── falco-healthcheck.service oneshot systemd unit
└── falco-healthcheck.timer runs health check every 6h
Every exception in rules.d/ is gated on the trustworthy_exe macro
(proc.is_exe_from_memfd = false and proc.is_exe_upper_layer = false). A
renamed-and-dropped attacker binary cannot ride into an allow-list entry
just by sharing a process name with a trusted program: the kernel-tracked
exec source has to be a regular on-disk file from the lower layer.
This is not a complete defense — an attacker who tampered with a binary at install time and waited will pass this check. Treat it as a tripwire against the trivial drop-and-exec bypass, not as an integrity guarantee.
falco-healthcheck.timer runs every 6 hours (and 5 min after boot) and
verifies:
falco-modern-bpf.serviceis activefalcosidekick.serviceis active/var/log/falco/alerts.jsonhas been written to in the last 24h- Falco's HTTP healthz endpoint at
127.0.0.1:8765/healthzresponds
On failure, it posts a single message to the configured Discord webhook and logs to journald. Designed to surface the day Falco silently dies.
Prerequisites:
- Falco (tested against 0.43.x) installed and running as
falco-modern-bpf.service. - Falcosidekick installed at
/usr/local/bin/falcosidekickwith a systemd service file. envsubst(fromgettext-base).sudoaccess.
Steps:
cp falco/.env.example falco/.env
chmod 600 falco/.env
$EDITOR falco/.env # fill in USERNAME, HOME_DIR, DISCORD_WEBHOOKURL
./falco/apply-rules.shapply-rules.sh:
- Loads
.env(refuses to run if it's not mode0600or0400). - Renders the templates with
envsubstto a tmpfs (whitelisted variables only — Falco's own%field.namesyntax is untouched). - Validates the rendered rules with
falco --validate. - Deploys to
/etc/falco/,/etc/falcosidekick/,/etc/logrotate.d/. - Reloads Falco via
systemctl kill -s HUP. - Restarts Falcosidekick.
- Verifies the deployed rules match the rendered source (drift check).
Re-run any time you edit the templates or .env.
The Discord webhook URL is the only secret. It lives in .env (gitignored,
mode 0600). At deploy time:
apply-rules.shwrites it to/etc/falco-secrets.env(mode0600,root:root).- A systemd drop-in (
/etc/systemd/system/falcosidekick.service.d/override.conf) loads that file asEnvironmentFile=. - Falcosidekick reads
DISCORD_WEBHOOKURLfrom its environment. - The webhook never appears in
/etc/falcosidekick/config.yamlor any file readable by non-root users.
If you fork this for a different sink (Slack, mattermost, etc.), the same pattern works — Falcosidekick reads almost every output's credentials from env vars.
Defined in .env (see .env.example):
| Variable | Description |
|---|---|
USERNAME |
Linux user owning the home directory the rules protect |
HOME_DIR |
Absolute home path; used in Claude/SSH detection rules |
DISCORD_WEBHOOKURL |
Discord webhook URL — secret |
Templated into the rules via envsubst. Other workstation assumptions
(i3lock, xfce4-screensaver, lightdm, virtiofsd, libvirtd, fwupdmgr, etc.) are
hardcoded in the rules template — fork and edit if your desktop differs.