picosnitch
elesiuta.github.io/picosnitchLinuxpicosnitch helps protect your security and privacy by "snitching" on anything that connects to the internet, letting you know when, how much data was transferred, and to where. It uses BPF to monitor network traffic per application, and per parent to cover those that just call others. It also hashes every executable, and will complain if some mischievous program is giving it trouble.
- Homepage:elesiuta.github.io/picosnitch
- GitHub:github.com/elesiuta/picosnitch
- Web info:web-check.xyz/check/elesiuta.github.io
picosnitch Source Code
Author
Description
Monitor network traffic per executable
Homepage
https://elesiuta.github.io/picosnitch/Repository
- LicenseGPL-3.0
- Created15 Jul 20
- Primary languageC
- Size5,810 KB
- Stars994
- Forks41
- Watchers994
Top Contributors
@elesiuta (864)
@aschaap (3)
@ahouts (1)
@gpchelkin (1)
@jeroenev (1)
@MJDSys (1)
@dependabot[bot] (1)
Recent Commits
Eric Lesiuta(10 Aug 26)
tui: make selected row more readable
Eric Lesiuta(10 Aug 26)
monitor: edge-trigger the conn-map near-capacity warning It fired on every 1 Hz drain for as long as a map sat at >=90% occupancy, so sustained high cardinality flooded error.log and, through the primary's toast-every-error path, the desktop as well. Warn on the transition into near-capacity and clear on the drop below, the way report_ring_drops guards its counters. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
notifications: dedup notify-send failures by return code notify-send's stderr carries a per-invocation pid and a GLib timestamp, so keying the dedup set on it never matched and every failed notification logged again -- filling error.log on a headless host with no X11. Key on the return code; the first failure still carries the full stderr. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
secondary: harden the write path against clock jumps, a full disk and retention_days=0 A backward wall-clock jump (NTP, suspend/resume, snapshot restore) made the write interval negative and stalled writes while the monitor kept buffering toward OOM, so it is monotonic now; contime still uses time.time(). A future clock at boot did the opposite, making the startup purge compute a cutoff past every row and delete the whole database -- skip the purge when the cutoff lands beyond the newest row, and order the unit after time-sync.target. Separately, a persistent write outage grew `transaction` unbounded, so cap it at RETRY_BUFFER_MAX and drop the oldest; a full disk raised out of maintain_database, which runs before the main loop's try/except and so crash-looped the daemon; and retention_days=0 deleted all history, though 0 is the usual "keep forever". Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
monitor: size the exec ring buffer from exec_ring_buffer_pages The option README documents as the mitigation for missed exec events stopped being read when the perf buffers went away, so it silently did nothing. A ringbuf's max_entries is its byte size, so the page count still expresses it, and the 256-page default is the 1 MiB already in bpf.c. Renamed because it now sizes one shared exec ring rather than per-cpu perf buffers for exec and dns; the old name warns and is ignored. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
bpf_wrapper: drop the perf-buffer path the ring buffer replaced exec/dns events now arrive on BPF ring buffers, leaving perf_buffer__new/poll/free, their callback types and the poll facade with no callers. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
monitor: deliver exec/dns events via BPF ring buffer, not perf buffer One shared buffer per stream instead of per-CPU arrays, so events cannot be lost to a single busy CPU and userspace wakes sooner -- which tightens the exec-before-hash race for short-lived processes. A full ring drops silently where perf buffers had lost_cb, so BPF counts overflows in .data globals the monitor reads each drain and logs. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
monitor: read each process's cmdline once per drain, not once per entry The drain read /proc/PID/cmdline for every drained connection, for the pid, its parent and its grandparent. A process that crafts a multi-megabyte cmdline and floods short connections thus made the drain re-read it thousands of times -- gigabytes per drain -- stalling the single-threaded monitor until it recorded nothing and was SIGKILL'd. So any local process could blind picosnitch and take the daemon down. Cache by pid within a drain, where a pid is one process; the shared string also pickles once downstream. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
monitor: optional mmap_ring_rx to attribute mmap-ring receives A process reading frames off an mmap'd RX ring issues no recvmsg and the kernel enqueues them in softirq, so the per-connection aggregation cannot see them -- both AF_PACKET PACKET_RX_RING (tcpdump -B, libpcap, IDS sensors) and AF_XDP RX. Record the owning process at ring setup (packet_set_ring, xsk_bind) where there is still a task, and look it up from the enqueue (tpacket_rcv, __xsk_rcv) to aggregate into the existing conn_stats_packet map. Off by default, since it adds a per-frame softirq hook the normal path avoids. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
service: sd_notify READY once BPF probes are attached (Type=notify) Type=simple reported the service active as soon as the process launched, seconds before the probes attached, so anything ordered After=picosnitch could start while it was still blind. Notify from the main process once the monitor signals over its previously unused q_out, using the raw protocol so the privileged daemon needs no python-systemd. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
secondary: memoize the per-connection cmdline processing within a write build_log_entries decoded and shlex.join'd the self, parent and grandparent cmdlines for every row -- about half its cost at high cardinality -- though a busy server or a scanning process emits many connections in one write that all share them. Memoize by raw string in a per-write dict, which stays fresh because it is rebuilt each write. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
monitor: drain BPF maps with the batched lookup-and-delete op drain_map restarted get_next_key(NULL) on every iteration, rescanning the bucket array from index 0 -- O(map_size * n), measured at 3.1 s to empty a full 65536-entry map and 11.7 s at 262144, against a 1 Hz drain. Raising conn_map_max_entries made it linearly worse, which is why a bigger map never helped. The batch op is O(n) and flat in map size; it is a 5.6 feature and the supported kernel floor is well above that, so the old loop goes rather than lingering as an unreachable fallback. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
secondary: bound reverse-DNS so a host scan can't stall byte accounting getnameinfo is a blocking round-trip per uncached address, serialized on the single write path, so a 16k-address sweep took ~110 s to land its rows and stalled every other process's accounting with it. Replace the lru_cache with a bounded cache that can be probed for a hit, and cap new lookups per write cycle; past the cap the address is still recorded and only its hostname waits. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
bench: build packet crafters with -fno-strict-aliasing They store addresses into packed header structs then checksum through a uint16_t*, which is undefined; at -O2 the compiler may hoist the checksum load above the store and emit frames whose IP checksum was computed with the destination still zero. The flag makes the type-punning defined. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
bench: count AF_PACKET application bytes as the whole frame The application hands the packet socket the entire frame, which is what sendto accepts and what the wire reference already measures. Counting only the payload understates it by 42 B per frame: 3% at the 1400 B default, but 66% at 64 B. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
pipeline: hand off connection events in chunked batches, not one per event A 16k-flow drain spent 73 s of its 76 s blocked in per-entry pipe.send_bytes: the GIL-bound reader stops draining while it processes each event, so the monitor stalls mid-drain and the in-kernel LRU evicts -- losing unrelated processes' traffic for the same window. Carry batches instead, in CONN_DRAIN_CHUNK-sized messages so a huge burst still flushes incrementally rather than landing as one lump 15-25 s later, and block on poll's own timeout rather than a fixed sleep that had capped the pipeline at one chunk per second (~2048 -> ~6144 connections/s). Live-feed serialization is skipped when nothing is subscribed. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
service: grant CAP_SYSLOG so the io_uring zcrx hook can attach The tcp_read_sock hook filters on io_zcrx_recv_skb's address, which /proc/kallsyms reports as zeros without CAP_SYSLOG under the common kptr_restrict=1 -- so zero-copy recv bytes went uncounted in the default deployment, not just hardened ones. ProtectKernelLogs=yes has to go, as systemd drops CAP_SYSLOG from the bounding set alongside it. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
cli: recreate an empty database rather than crash-loop on its version A version mismatch was always kept for a future migration, but a 0-byte file (sqlite reports version 0) has nothing to migrate, so check_database rejected it on every boot. An ordinary stop, delete, start leaves exactly that behind, since the secondary briefly outlives the stop and recreates the file. Recreate when no schema exists. Assisted-by: Claude:Opus-5
Eric Lesiuta(10 Aug 26)
monitor: attribute raw-frame paths (AF_PACKET, TUN, AF_XDP) Traffic that never crosses a socket sendmsg/recvmsg went unrecorded: AF_PACKET raw frames, complete packets written to or read from /dev/net/tun, and AF_XDP copy-mode TX. Each has a hook that runs in the acting task -- packet_sendmsg/packet_recvmsg, tun_get_user/tun_do_read, xsk_build_skb -- feeding one conn_stats_packet map keyed by (pid, netns), since a raw socket carries no L3/L4 identity and the peer lives in the app-crafted frame. What has no owning task is left out: the softirq enqueue, a reader taking frames off an mmap'd RX ring, zero-copy TX, and vhost-serviced tap reads. Every hook is best-effort and simply absent when its kernel option is off. Assisted-by: Claude:Opus-5
Eric Lesiuta(05 Aug 26)
bench: fix ty check error
Eric Lesiuta(05 Aug 26)
bench: run sudo python3 lib/run.py --tools all Full run on a freshly provisioned machine.
Eric Lesiuta(05 Aug 26)
bench: fix tool setups and preflight for fresh installs, report failed setups - bcc built and installed successfully into /usr, not the /usr/local the adapters read: bcc's CMakeLists force-overrides CMAKE_INSTALL_PREFIX to /usr whenever cmake's default was used, so build_bcc.sh has to pass it explicitly. It now does, and verifies the artifacts it promised rather than leaving the adapter to report a version mismatch it cannot explain - little snitch's install swallowed every failure into "not installable in this environment", scoring N/A -- a missing capability -- for a dead download. It now raises, and its version stops being pinned: obdev publishes only the current release, under a versioned filename - a tool whose setup failed has no scenario rows, which the reports counted as zeros in every bucket and drew as blank N/A cells. It is now marked "not measured", listed under a new Run health section, and the run exits non-zero. Its recorded errors get their own heading in the scorecard notes instead of nesting under the previous tool's - preflight what the matrix assumes (pipx, docker + the alpine image, the sctp module, the compiled helpers) so a missing prerequisite fails in the first minute instead of scoring as a tool result hours later - an aborted tool writes its own results.json: it used to leave the previous run's file in place to be republished as this run's - state the layer each Sysdig row was scored at -- s12 is scored on whole frames while the page claimed application bytes throughout -- and correct the RATE comment: the paced transfers run 4.8s at 24 MiB, not "every paced transfer >5s" Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Eric Lesiuta(03 Aug 26)
bench: rerun sudo python3 lib/run.py --tools all
Eric Lesiuta(03 Aug 26)
bench: correct Sysdig mmsg bytes and the wire-tool trial baseline - restore Sysdig's sendmmsg/recvmmsg byte counts: evt.rawres is a per-message byte count, never a message count, so scoring those cells N/A dropped both a real overshoot (recvmmsg 1.97x, one duplicate event per call) and a real pass (sendmmsg 1.03x) - baseline NetHogs and bandwhich on the freshest values printed, the filling refresh block included: a baseline one refresh stale counted the previous trial's tail as this trial's unattributed traffic - read an unknown-bucket delta below 1% of the scenario reference as background traffic on the monitored interfaces, not as the flow under test - stamp the findings with the date the run finished, trim its preamble, and take the docs comparison intro from the bench README instead of repeating it Co-authored-by: Claude Fable 5 <[email protected]>
Eric Lesiuta(02 Aug 26)
bench: rerun sudo python3 lib/run.py --tools all
Eric Lesiuta(02 Aug 26)
bench: fix remaining comparison errors - score coherent named and unknown bandwidth samples without conflating attribution - scope OpenSnitch journal reads and reject incomplete raw-IP transfers - correct Sysdig byte references for frame and message-count syscalls Assisted-by: Codex:GPT-5.6-sol
Eric Lesiuta(30 Jul 26)
bench: results from a full rerun under the per-layer scoring All ten configurations completed in one session (2026-07-29); every control passed on the first attempt in both directions, no trial scored ERROR, and no run notes were recorded. The movements against the previous results follow the parent commit's fixes, plus the run-to-run variance of the tools that have it: BCC tcplife's three detection PARTIALs become PASS (a row named by the unique executable is process attribution); the bpftrace cell inflated by the unsigned comparison becomes PASS; bandwhich sees the UDP/53 scenario with --show-dns and is scored against the IP payload it counts; Sniffnet gains two bandwidth cells from the decimal units and its flaky cell count drops to zero; three of Little Snitch's detection FAILs become PARTIAL under the misattribution check; packet-layer loopback bandwidth is N/A with no wire reference. Sysdig, picosnitch, and tcptop are unchanged. Co-Authored-By: Claude Fable 5 <[email protected]>
Eric Lesiuta(30 Jul 26)
bench: score each tool at the layer it counts, and fix harness defects that under-scored tools Configuration and scoring defects, each of which under-scored a tool: - bandwhich drops port 53 unless --show-dns, so the small-packet UDP/53 scenario measured nothing. The flag is now passed - bandwhich counts ip_packet.payload(), the L4 segment, so scoring it against whole frames charged it one IP and one Ethernet header per packet. It is scored against the L3 count minus one IP header per packet, and polls to that same reference. NetHogs and Sniffnet keep the whole-frame reference. Each layer's reference is defined once, in harness.layer_ref - Sniffnet formats its figures with decimal SI multipliers; the OCR parser used binary ones, inflating every reading by up to 5%. It now uses 1000-based units - Little Snitch was polled on its own shorter budget (21 s deadline, 2.4 s plateau) while every other cumulative tool shares 54 s / 15 s; it now polls with the shared constants - a tcplife row carries the unique executable name, so it is per-process attribution; it counted only as flow detection, leaving s03, s21 and s22 PARTIAL for BCC - the bpftrace script compared a signed int as unsigned, so a negative copied count from tcp_cleanup_rbuf passed the positive filter and was summed, reporting terabytes for a 24 MiB transfer. The value is cast before use - the loopback scenario has no peer namespace, so it has no wire measurement; it no longer copies application bytes into the wire fields, and tools scored at a packet layer are N/A on its bandwidth Failures that were silent are now fatal: a competing monitor during a tool's own run, a failed counter reset, and a reliable transfer that stops short of the bytes requested. Generators report the byte count they were asked for so the last of those can be checked. The BCC build script no longer skips a rebuild when the installed library is a different version. Harness faults are kept out of tool verdicts, and one tool's faults out of the rest of the run: - a competing monitor that will not exit fails only the tool whose isolation it breaks, recorded as that tool's setup error; the remaining tools still run and report (previously a logged warning, and the tainted run continued) - a monitor process found dead at trial start scores ERROR, not FAIL: it can observe nothing, and that is the harness's fault domain - a failed screenshot left the previous trial's image on disk for tesseract to read as the current trial's panel; the files are removed first, a nonzero screenshot exit is an invalid read, and Xvfb is relaunched before the next trial when it died or a read failed - a generator failure no longer puts ERROR in the bandwidth column of a tool that has no bandwidth capability - generators launched as transient units are capped at 120 s so a timed-out trial cannot leave one running into later trials; the launcher also clears bcc/bpftrace/sysdig/screenshot leftovers and stops the apt timers so recorded package versions hold for a session Per-tool tables show the reference actually scored against rather than raw L3 bytes, both directions of a duplex ratio, and the first-trial disclaimer on every page. Result notes record the unknown bucket a tool used rather than reporting nothing, no longer append a single trial's note to a cell whose trials disagreed, and list the run notes (up to five) rather than only the first. Documentation: tcptop probes tcp_sendmsg and tcp_recvmsg, not tcp_cleanup_rbuf (code comments now agree); N/A means a capability the tool does not offer, so a TCP-only configuration is N/A on non-TCP scenarios while a general monitor that misses a protocol fails; the misattribution-vs-miss check exists only where a tool's output is keyed by flow or an unknown bucket rather than by process name, and the README says which extractions those are; the modal rule sets N/A trials aside unless all trials are N/A; a control also fails on zero bytes in either direction; the harness is destructive and the README says so before the commands, with the prerequisites it assumes. Co-Authored-By: Claude Fable 5 <[email protected]>
Eric Lesiuta(29 Jul 26)
bench: results for the corrected comparison Full matrix on 2026-07-28: 10 configurations x 24 scenarios x 5 trials, each run in isolation. Sniffnet detection is 13 PASS / 7 PARTIAL / 4 FAIL and bandwidth 16 PASS. Its four detection failures are the scenarios it never lists: s05, s08, s10 and s16. Its cells now distinguish naming the program, listing the traffic under its unattributed row, and showing no row at all, and its trials disagree on one cell. picosnitch, Sysdig, OpenSnitch, Little Snitch, bpftrace and both BCC configurations are unchanged. bandwhich s04 PARTIAL -> PASS and s23 PASS -> PARTIAL, NetHogs s21 PASS -> PARTIAL. Co-Authored-By: Claude Fable 5 <[email protected]>
Eric Lesiuta(29 Jul 26)
bench: correct the comparison and simplify its rules Corrections where a published claim did not match the code or the data: - Sysdig reports roughly double the recvmmsg bytes rather than missing them, so it is no longer listed among the scenarios whose bytes it cannot see - bandwidth PARTIAL is the +-25% band; there was no "attribution split" case - AF_PACKET is scored against the nftables reference like every other scenario, not against the generator's byte count - footnotes cover PARTIAL and FAIL, not PARTIAL alone - the bpftrace program is a one-line -e expression - Sniffnet's OCR misreads figures rather than rounding them - ss attributes sockets to processes, so it is not excluded for that - the NetHogs and bandwhich command lines were missing arguments that change what they capture - Little Snitch's missing-row note asserted a cause the adapter cannot observe Scoring is one rule for every tool: a cell takes the modal verdict, ties broken toward the worse one, and is marked when its trials disagree. Sniffnet's best-of-5 accommodation is gone. Sniffnet is measured only from what it displays. It restarts before each trial so its Program panel lists only that trial's traffic, the panel is cropped before OCR so program names read exactly, its unattributed "?" row is read as its own bucket like the unknown buckets of the other wire-layer tools, and a scenario it does not list scores as reported-nothing. The separate packet capture is removed. Result notes state what each tool reported per trial, so a PARTIAL, FAIL or disagreement can be read without consulting the harness. Loopback direction is taken from the peer port, since both endpoints share an address; this affects s16 only. Installers verify the version they claim to pin. Versions and the run date are read at run time and published, replacing hand-maintained values in the README. Trimming: the Measures column contradicted the detection scorecard for four tools; the Reproducibility section, the S12 paragraph and the resource-table sentences restated text above them; each scenario's mechanism note was repeated in every affected cell of both scorecards. Co-Authored-By: Claude Fable 5 <[email protected]>
picosnitch Website
Website
picosnitch
Per-executable network bandwidth monitoring for Linux
Redirects
Redirects to https://elesiuta.github.io/picosnitch/
Security Checks
1 security checks failed (64 passed)
- Risky Category Detected
Server Details
- IP Address185.199.109.153
- Hostnamecdn-185-199-109-153.github.com
- LocationFrancisco,Indiana,United States of America,NA
- ISPGitHub Inc.
- ASNAS54113
Categories
Some proxies may block this service, as it falls into the following categories
- Free Hosting
Associated Countries
US
Safety Score
Website marked as moderately safe
90%
Blacklist Check
elesiuta.github.io was found on 0 blacklists
- AntiSocial Blacklist
- Artists Against 419
- Badbitcoin
- Bambenek Consulting
- CERT Polska
- CoinBlockerLists
- CRDF
- CryptoScamDB
- EtherAddressLookup
- EtherScamDB
- Fake Website Buster
- MetaMask EthPhishing
- NABP Not Recommended Sites
- OpenPhish
- PetScams
- PhishFeed
- PhishFort
- Phishing.Database
- PhishStats
- PhishTank
- Phishunt
- RPiList Not Serious
- Scam.Directory
- SecureReload Phishing List
- Spam404
- StopGunScams
- Suspicious Hosting IP
- ThreatFox
- ThreatLog
- TweetFeed
- URLhaus
- ViriBack C2 Tracker
Website Preview
picosnitch Reviews
More Intrusion Detection
An 802.11 layer2 wireless network detector, sniffer, and intrusion detection system.
OSSEC is an Open Source host-based intrusion detection system, that performs log analysis, integrity checking, monitoring, rootkit detection, real-time alerting and active response.
SNARE (System iNtrusion Analysis and Reporting Environment) is a series of log collection agents that facilitate centralized analysis of audit log data. Logs from the OS are collected and audited. Full remote access, through a web interface easy to use manually, or by an automated process.
Not Open SourceZeek (formally Bro) Passively monitors network traffic and looks for suspicious activity.
About the Data: picosnitch
Edit picosnitch Data
You can edit picosnitch's entry in this section of awesome-privacy.yml by submitting a PR to our GitHub repo.
Note that some of the information shown above has been aggregated from external
sources, a list of these can be found data documentation.
Origin Data
Modify Data
API
You can access picosnitch's data programmatically via our API. Simply make a GET request to:
https://api.awesome-privacy.xyz/v1/services/picosnitchThe REST API is free, no-auth and CORS-enabled. To learn more, view the API Docs or read the API Usage Guide.
Share picosnitch
Help your friends compare Intrusion Detection, and pick privacy-respecting software and services.
Share picosnitch and Awesome Privacy with your network!