Metadata-Version: 2.5
Name: nettattle
Version: 0.1.0
Summary: Monitor outbound network connections per process and record data-transfer volumes
Author: Mitesh Singh Jat
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: beacon,exfiltration,monitoring,nettop,network,security,ss
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Networking :: Monitoring
Requires-Python: >=3.10
Requires-Dist: psutil>=5.9
Description-Content-Type: text/markdown

<p align="center">
  <img src="nettattle/static/logo.svg" alt="NetTattle logo: a speech bubble with an arrow breaking out of it" width="128">
</p>

# NetTattle

*Catch the small, fast, easy-to-miss thing on your network.*

A lightweight CLI that watches outbound TCP connections, maps each to its owning
process, and records how many bytes each process sends and receives over time.
It is meant to surface quiet background exfiltration: processes that send small,
steady amounts of data.

Supported: macOS (`nettop`) and Linux (`ss`, iproute2 with kernel 4.x+).
Requires Python 3.10+; the only dependency is `psutil`.

## Installation

```bash
pipx install nettattle  # or: uv tool install nettattle
pip install nettattle   # into an existing environment
```

From a checkout, `uv build` produces the wheel and sdist in `dist/`, and
`pip install dist/nettattle-*.whl` installs it. The package provides a
`nettattle` command (also `python -m nettattle`).

## Usage

```bash
nettattle --monitor                  # sample every 5s into ./nettattle.db
nettattle --monitor --interval 10
nettattle --report                   # top talkers by bytes sent
nettattle --report --since 1h --limit 10
nettattle --report --by-destination  # per process + remote endpoint
nettattle --report --resolve         # ...plus reverse-DNS host names
nettattle --report --suspicious      # beacon-like and upload-heavy flows
nettattle --report --processes       # traffic per executable + code signature
nettattle --monitor --retain 30d     # also delete data older than 30 days
```

Options: `--db PATH` (default `nettattle.db`), `--include-loopback`, `-v/--verbose`.

### Process identity (`--processes`)

A process name is chosen by whoever starts the process, so malware can call itself
`updaterd`. For every process with traffic, the monitor also records the
**executable path, user, parent process and start time**, and on macOS the
**code signature** (Apple, Developer ID + team, App Store, ad-hoc, unsigned, or
invalid). The start time keeps two processes that reused the same PID apart.

`--report --processes` lists traffic per name + executable + signature, with
flagged identities first:

| Flag | Meaning |
|---|---|
| `invalid-signature` | signed, but the code was modified after signing (macOS) |
| `unsigned` | no code signature (macOS) |
| `temp-exe` | executable lives in a temp directory (`/tmp`, `/var/tmp`, `/dev/shm`, macOS `/var/folders/…/T/`) |
| `exe-deleted` | the executable was deleted after the process started |

Signatures are checked with `/usr/bin/codesign --verify --strict --all-architectures`
(by absolute path), once per binary. `codesign -dv` alone is not enough: a tampered
binary still *displays* its original Apple authority. Ad-hoc signatures are shown but
not flagged, because locally built tools (Homebrew, uv's Python) are ad-hoc signed.
Command lines are **not** recorded, as they often contain secrets. Use
`--no-identity` to turn identity recording off.

The Identity column in `--suspicious` and in the dashboard shows the same
information. Traffic recorded before this feature existed, or from processes that
exited before they could be inspected, shows `?`.

### Retention (`--retain`)

By default nothing is deleted. `--monitor --retain 30d` deletes traffic (and
process records) older than 30 days at startup and then every hour, in small
batches so reports and the dashboard are not blocked. SQLite reuses the freed
space; the file itself does not shrink unless you run `VACUUM`.

### Suspicious flows (`--suspicious`)

Each flow (process + remote IP + port) is checked for two behaviours, using the
data already recorded:

- **beacon**: regular timing and similar burst sizes, as malware check-ins tend to
  have. The score (0-100) combines how many gaps between active samples match the
  median gap with how many bursts are within 25% of the median size. Flows scoring
  70 or more with at least 6 events are flagged. Periods shorter than three
  sampling intervals must match exactly, because looser matching cannot tell them
  apart from random traffic.
- **upload-heavy**: sent at least 16 KiB and at least 3x what was received.
  Exfiltration sends more than it receives; normal browsing is the reverse.

Legitimate push and keepalive channels (OS notification services, chat apps) are
beacons too and will be flagged. The flag describes behaviour, not intent.
Combine with `--since` and `--resolve` as needed. The dashboard shows the same
flows in a "Suspicious flows" panel.

### Host names (`--resolve`)

`--resolve` looks up a reverse-DNS (PTR) name for each remote IP in the report
and shows it in a Host column (it implies `--by-destination`). Things to know:

- **Names are forward-confirmed.** Whoever controls an IP sets its PTR record, so
  a malicious server can claim to be `updates.apple.com`. A name is shown plainly
  only if it resolves back to the same IP; otherwise it is marked `(unverified)`.
- **Many IPs have no useful name.** Cloud and CDN addresses usually have no PTR
  record or a generic one (`ec2-…amazonaws.com`); that is not the hostname the
  application connected to.
- **Lookups happen only at report time**, never while monitoring, all at once
  within `--resolve-timeout` seconds (default 5). Lookups that don't finish show
  `? (timeout)`.
- **Results are cached** in `<db>.dns-cache.json` (or `--dns-cache PATH`): 24 h for
  names, 1 h for "no name". Timeouts are not cached.

### Web dashboard (`--serve`)

```bash
nettattle --serve  # http://127.0.0.1:8765/
nettattle --serve --port 9000 --db nettattle.db
```

A read-only dashboard over the same database: totals, traffic over time for the
top processes, a talkers table with an **Active** column (share of sampled
intervals with traffic), and a destinations table with optional FCrDNS host names.
It auto-refreshes every 10 seconds and needs no extra dependencies.

- It binds to `127.0.0.1` by default. `--host 0.0.0.0` exposes it to your network
  **with no authentication**; the tool prints a warning when you do this.
- Requests whose `Host` header is not `localhost`, an IP address or the `--host`
  value are rejected (protection against DNS rebinding). Only GET is allowed.
- Process and host names come from the monitored system and are treated as
  untrusted: the page never renders them as HTML, and a strict
  Content-Security-Policy blocks inline scripts.

`--report` and `--serve` can run while `--monitor` is writing (SQLite WAL mode).

In the report, **Intervals** counts the sampling intervals in which a process moved
data. A process with a small byte total but a high interval count is talking
steadily in the background.

## Privileges

Without root the tool still runs, but it sees less: on Linux, other users' sockets
are recorded as `<unknown>` (pid `-1`). For full visibility, run the monitor as
root. Tools installed with pipx or `uv tool` are usually not on root's `PATH`, so
pass the full path:

```bash
sudo "$(command -v nettattle)" --monitor --db /var/db/nettattle.db
```

The database is created relative to the current directory unless you pass
`--db`; one first created under `sudo` is owned by root.

## Development

```bash
uv sync                            # .venv with the project installed
uv run nettattle-dev.py --monitor  # same CLI as nettattle
uv run python -m unittest discover tests/
uv build                           # wheel + sdist into dist/
```

From a checkout, run as root with `sudo .venv/bin/python nettattle-dev.py --monitor`.
Do not use `sudo uv run`: it can leave root-owned files in `.venv`.

## How counting works

The OS reports cumulative counters per socket. Each cycle, NetTattle subtracts
the previous sample for the same socket (identified by local port and remote
endpoint) and stores only non-zero per-interval deltas. The first sample after
startup is a baseline and is not recorded. A counter that goes backwards is
treated as a fresh socket.

## Tests

Parser tests run against real captured `nettop` and `ss` output in
`tests/fixtures/`, so they pass on any OS.

## Name

*Net* + *tattle*: it tells you what your machine has been saying on the network
behind your back. NetTattle is an independent project, inspired by Little Snitch
but not affiliated with or endorsed by Objective Development, its makers.
