netaudit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netaudit-0.1.0/.claude/commands/commit.md +22 -0
- netaudit-0.1.0/.github/pull_request_template.md +11 -0
- netaudit-0.1.0/.github/workflows/ci.yml +60 -0
- netaudit-0.1.0/.gitignore +30 -0
- netaudit-0.1.0/.instructions/plan.md +271 -0
- netaudit-0.1.0/.readthedocs.yaml +16 -0
- netaudit-0.1.0/CLAUDE.md +111 -0
- netaudit-0.1.0/LICENSE +201 -0
- netaudit-0.1.0/Makefile +28 -0
- netaudit-0.1.0/PKG-INFO +302 -0
- netaudit-0.1.0/README.md +69 -0
- netaudit-0.1.0/docs/allowlist-dsl.md +138 -0
- netaudit-0.1.0/docs/architecture.md +116 -0
- netaudit-0.1.0/docs/cli-reference.md +118 -0
- netaudit-0.1.0/docs/index.md +64 -0
- netaudit-0.1.0/mkdocs.yml +46 -0
- netaudit-0.1.0/netaudit/__init__.py +3 -0
- netaudit-0.1.0/netaudit/allowlist.py +130 -0
- netaudit-0.1.0/netaudit/cli.py +109 -0
- netaudit-0.1.0/netaudit/integrations/__init__.py +1 -0
- netaudit-0.1.0/netaudit/parser.py +193 -0
- netaudit-0.1.0/netaudit/py.typed +0 -0
- netaudit-0.1.0/netaudit/reporter.py +110 -0
- netaudit-0.1.0/netaudit/runner.py +66 -0
- netaudit-0.1.0/pyproject.toml +58 -0
- netaudit-0.1.0/tests/conftest.py +0 -0
- netaudit-0.1.0/tests/integration/__init__.py +0 -0
- netaudit-0.1.0/tests/integration/conftest.py +1 -0
- netaudit-0.1.0/tests/integration/egress_target.py +84 -0
- netaudit-0.1.0/tests/integration/test_end_to_end.py +104 -0
- netaudit-0.1.0/tests/test_allowlist.py +185 -0
- netaudit-0.1.0/tests/test_cli.py +211 -0
- netaudit-0.1.0/tests/test_parser.py +177 -0
- netaudit-0.1.0/tests/test_reporter.py +128 -0
- netaudit-0.1.0/tests/test_version.py +6 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Look at the current git diff (staged + unstaged) and create a GPG-signed conventional commit.
|
|
2
|
+
|
|
3
|
+
Steps:
|
|
4
|
+
1. Run `git diff HEAD` to see all changes (staged and unstaged). If nothing, run `git status` to check for untracked files.
|
|
5
|
+
2. Determine the commit type from the diff:
|
|
6
|
+
- `feat`: new user-visible functionality
|
|
7
|
+
- `fix`: bug fix
|
|
8
|
+
- `refactor`: restructuring without behavior change
|
|
9
|
+
- `test`: test additions or changes only
|
|
10
|
+
- `docs`: documentation only
|
|
11
|
+
- `chore`: tooling, config, build, CI, deps
|
|
12
|
+
3. Stage all relevant changes with `git add` (specific files, not `-A`).
|
|
13
|
+
4. Write a single-line commit message: `<type>(<scope>): <short description>` — scope is optional, description is lowercase, no trailing period, under 72 chars total.
|
|
14
|
+
5. Commit with `git commit -S -m "<message>"` (GPG-signed, no co-author line).
|
|
15
|
+
|
|
16
|
+
Rules:
|
|
17
|
+
- No co-author trailer.
|
|
18
|
+
- No body or footer unless the change genuinely needs explanation.
|
|
19
|
+
- Do not skip hooks (`--no-verify`).
|
|
20
|
+
- If GPG signing fails, report the error and stop — do not commit unsigned.
|
|
21
|
+
- Do not push.
|
|
22
|
+
- Never reference phase numbers (e.g. "phase 0", "phase 1") in the commit message — describe what was done, not which plan phase it belongs to.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Title
|
|
2
|
+
<!-- conventional commit format: type: short subject (e.g. feat: add HTTP/SSE transport) -->
|
|
3
|
+
|
|
4
|
+
## What
|
|
5
|
+
<!-- one or two sentences describing what was built or changed -->
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
<!-- what problem it solves or what it enables -->
|
|
9
|
+
|
|
10
|
+
## Acceptance criteria
|
|
11
|
+
- [ ] <!-- mark with x if the criterion is verified -->
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
jobs:
|
|
9
|
+
lint:
|
|
10
|
+
name: Lint
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.11"
|
|
17
|
+
- name: Install dev dependencies
|
|
18
|
+
run: pip install -e ".[dev]"
|
|
19
|
+
- name: ruff check
|
|
20
|
+
run: ruff check .
|
|
21
|
+
- name: ruff format
|
|
22
|
+
run: ruff format --check .
|
|
23
|
+
- name: mypy
|
|
24
|
+
run: mypy netaudit/
|
|
25
|
+
- name: Install docs dependencies
|
|
26
|
+
run: pip install -e ".[docs]"
|
|
27
|
+
- name: mkdocs build
|
|
28
|
+
run: mkdocs build --strict
|
|
29
|
+
|
|
30
|
+
test:
|
|
31
|
+
name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
|
|
32
|
+
runs-on: ${{ matrix.os }}
|
|
33
|
+
strategy:
|
|
34
|
+
matrix:
|
|
35
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
36
|
+
python-version: ["3.11", "3.12"]
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: actions/setup-python@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: ${{ matrix.python-version }}
|
|
42
|
+
- name: Install dev dependencies
|
|
43
|
+
run: pip install -e ".[dev]"
|
|
44
|
+
- name: Run tests
|
|
45
|
+
run: pytest --cov=netaudit --cov-fail-under=80
|
|
46
|
+
|
|
47
|
+
integration:
|
|
48
|
+
name: Integration Tests (strace)
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@v4
|
|
52
|
+
- uses: actions/setup-python@v5
|
|
53
|
+
with:
|
|
54
|
+
python-version: "3.11"
|
|
55
|
+
- name: Install strace
|
|
56
|
+
run: sudo apt-get install -y strace
|
|
57
|
+
- name: Install dev dependencies
|
|
58
|
+
run: pip install -e ".[dev]"
|
|
59
|
+
- name: Run integration tests
|
|
60
|
+
run: pytest -m integration -v
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.eggs/
|
|
12
|
+
.mypy_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
htmlcov/
|
|
17
|
+
|
|
18
|
+
# Virtual environment
|
|
19
|
+
.venv/
|
|
20
|
+
|
|
21
|
+
# netaudit
|
|
22
|
+
.strace-out/
|
|
23
|
+
|
|
24
|
+
# Docs
|
|
25
|
+
site/
|
|
26
|
+
|
|
27
|
+
# Distribution
|
|
28
|
+
*.whl
|
|
29
|
+
*.tar.gz
|
|
30
|
+
MANIFEST
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Netaudit Development Plan (Refined)
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
netaudit is a Python CLI/library that wraps processes under `strace`, captures `connect()` syscalls, and validates them against a declarative YAML allowlist. Goal: CI-native network egress auditing — commit a config, run tests, get pass/fail.
|
|
6
|
+
|
|
7
|
+
Starting from scratch on `main` (README + LICENSE only, no remote). Each phase produces a testable, CI-validated increment.
|
|
8
|
+
|
|
9
|
+
### Decisions
|
|
10
|
+
|
|
11
|
+
- **Versioning:** 0.x semver until API stabilizes, then 1.0
|
|
12
|
+
- **First public release:** Phase 3 (when CLI is usable), tagged 0.1.0
|
|
13
|
+
- **Docs:** MkDocs Material, deployed to GitHub Pages — deferred until Phase 3 (docstrings + README before that)
|
|
14
|
+
- **CI:** Ubuntu-only for integration tests, cross-platform for unit tests
|
|
15
|
+
- **Changelog:** None until v1
|
|
16
|
+
- **pytest plugin:** Session-level first, per-test attribution second commit in same phase
|
|
17
|
+
- **Type checking:** mypy strict from Phase 0, `py.typed` marker included
|
|
18
|
+
- **Skills:** Only `/commit` from day one; others added when needed
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Phase Overview
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Phase 0: Scaffold ──► Phase 1: Core Engine ──► Phase 2: Runner + Integration
|
|
26
|
+
│ (parser+allowlist+ (strace subprocess,
|
|
27
|
+
│ reporter) end-to-end tests)
|
|
28
|
+
│ │
|
|
29
|
+
▼ ▼
|
|
30
|
+
CI v1 (lint+test) CI v2 (+integration on Ubuntu)
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
Phase 3: CLI + Docs
|
|
34
|
+
(click CLI, mkdocs,
|
|
35
|
+
first release 0.1.0)
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
Phase 4: pytest Plugin
|
|
39
|
+
(session-level, then
|
|
40
|
+
per-test attribution)
|
|
41
|
+
│
|
|
42
|
+
▼
|
|
43
|
+
Phase 5: Release Engineering
|
|
44
|
+
(PyPI, Docker, GH Pages,
|
|
45
|
+
hardening)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Phase 0 — Scaffold
|
|
51
|
+
|
|
52
|
+
**Goal:** Buildable, installable, lintable skeleton with CI.
|
|
53
|
+
|
|
54
|
+
- `pyproject.toml`: metadata, deps (`click`, `pyyaml`), dev deps (`pytest`, `ruff`, `mypy`, `pytest-cov`), entry point `netaudit = netaudit.cli:main`
|
|
55
|
+
- Package: `netaudit/__init__.py` (version string), `netaudit/integrations/__init__.py`
|
|
56
|
+
- `py.typed` marker
|
|
57
|
+
- `.gitignore` (Python template + `.strace-out/` + `.venv/`)
|
|
58
|
+
- Ruff config: `line-length=100`, `select = ["E", "F", "W", "I"]`
|
|
59
|
+
- Mypy config: `strict = true`
|
|
60
|
+
- Empty `tests/conftest.py`
|
|
61
|
+
- `.venv` local development — created via `python -m venv .venv`, editable install with dev deps
|
|
62
|
+
- `Makefile` with `venv`, `lint`, `test`, `clean` targets for one-command local workflow
|
|
63
|
+
- GitHub Actions CI (`.github/workflows/ci.yml`): lint (ruff check + ruff format --check + mypy), test (pytest, no tests yet)
|
|
64
|
+
- `.claude/commands/commit.md`: conventional commit skill — auto-detects type from diff, GPG-signed, no co-author
|
|
65
|
+
|
|
66
|
+
**Exit:** `make venv && make lint && make test` works, CI green, `netaudit --help` prints placeholder.
|
|
67
|
+
|
|
68
|
+
**Files:**
|
|
69
|
+
```
|
|
70
|
+
pyproject.toml
|
|
71
|
+
Makefile
|
|
72
|
+
.gitignore
|
|
73
|
+
netaudit/__init__.py
|
|
74
|
+
netaudit/py.typed
|
|
75
|
+
netaudit/cli.py # placeholder: click group, --version
|
|
76
|
+
netaudit/integrations/__init__.py
|
|
77
|
+
tests/conftest.py
|
|
78
|
+
.github/workflows/ci.yml
|
|
79
|
+
.claude/commands/commit.md
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Phase 1 — Core Engine (Parser + Allowlist + Reporter)
|
|
85
|
+
|
|
86
|
+
**Goal:** Parse strace output, match against rules, report violations. All tested without strace.
|
|
87
|
+
|
|
88
|
+
**Parser** (`netaudit/parser.py`):
|
|
89
|
+
- `ConnectEvent` dataclass: `pid`, `timestamp`, `family`, `addr`, `port`, `raw_line`, `result`
|
|
90
|
+
- `StraceParser.parse_line(line) -> ConnectEvent | None`
|
|
91
|
+
- `StraceParser.parse_stream(lines: Iterable[str]) -> list[ConnectEvent]`
|
|
92
|
+
- Handle: AF_INET, AF_INET6, AF_UNIX, AF_NETLINK
|
|
93
|
+
- Handle: EINPROGRESS (non-blocking connect — not a failure), resumed lines (`<... connect resumed>`)
|
|
94
|
+
|
|
95
|
+
**Allowlist** (`netaudit/allowlist.py`):
|
|
96
|
+
- `Rule` protocol: `matches(event: ConnectEvent) -> bool`
|
|
97
|
+
- Concrete: `IPv4Rule(cidr)`, `IPv6Rule(cidr)`, `UnixSocketRule(path_glob)`, `NetlinkRule`
|
|
98
|
+
- `AllowList`: load from YAML, `includes_builtins: true` by default (loopback 127.0.0.0/8, ::1/128, all AF_UNIX, all AF_NETLINK)
|
|
99
|
+
- `AllowList.is_allowed(event) -> bool`
|
|
100
|
+
|
|
101
|
+
**Reporter** (`netaudit/reporter.py`):
|
|
102
|
+
- `Violation`: groups events by `(family, addr, port)` — tracks PIDs, count, first timestamp
|
|
103
|
+
- `Reporter.check(events, allowlist) -> list[Violation]`
|
|
104
|
+
- `Reporter.format(violations, stream)` — box-formatted human-readable output
|
|
105
|
+
|
|
106
|
+
**Tests:** hardcoded strace lines for parser (all families, EINPROGRESS, resumed, malformed), YAML loading + CIDR matching + builtins for allowlist, grouping + formatting for reporter.
|
|
107
|
+
|
|
108
|
+
**CI update:** Tests run with `--cov=netaudit --cov-fail-under=80`.
|
|
109
|
+
|
|
110
|
+
**Exit:** All three modules compose together. Tests pass, coverage >= 80%.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Phase 2 — Runner + Integration Tests
|
|
115
|
+
|
|
116
|
+
**Goal:** Spawn strace, capture output, validate end-to-end on real syscalls.
|
|
117
|
+
|
|
118
|
+
**Runner** (`netaudit/runner.py`):
|
|
119
|
+
- `StraceRunner.run(command, output_path) -> CompletedProcess` — spawn under `strace -e trace=connect -f -tt -o <path>`
|
|
120
|
+
- `StraceRunner.start(command, output_path) -> StraceProcess` / `.stop() -> CompletedProcess`
|
|
121
|
+
- Runtime check: `StraceNotFoundError` if `shutil.which("strace")` is None
|
|
122
|
+
|
|
123
|
+
**Test target** (`tests/integration/egress_target.py`):
|
|
124
|
+
- Starts a local `http.server`, connects to it
|
|
125
|
+
- Opens a Unix socket
|
|
126
|
+
- Triggers DNS/netlink via `socket.getaddrinfo`
|
|
127
|
+
- Attempts external connect to `198.51.100.1:443` (TEST-NET-2 — won't route, no flaky external deps)
|
|
128
|
+
|
|
129
|
+
**Integration tests** (`tests/integration/test_end_to_end.py`, marked `@pytest.mark.integration`):
|
|
130
|
+
- strace output file created and non-empty
|
|
131
|
+
- Parser extracts expected event types from real output
|
|
132
|
+
- External IP `198.51.100.1` produces violation
|
|
133
|
+
- Loopback + unix + netlink allowed by default
|
|
134
|
+
- Custom allowlist passes external IP
|
|
135
|
+
|
|
136
|
+
**CI update:** Add `integration` job on `ubuntu-latest` (installs strace via apt). Unit tests remain cross-platform.
|
|
137
|
+
|
|
138
|
+
**Exit:** Full pipeline works end-to-end. Integration tests green in CI.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Phase 3 — CLI + Docs (First Release 0.1.0)
|
|
143
|
+
|
|
144
|
+
**Goal:** User-facing CLI, documentation site, tagged 0.1.0.
|
|
145
|
+
|
|
146
|
+
**CLI** (`netaudit/cli.py`):
|
|
147
|
+
- `netaudit run --allowlist <yaml> -- <command>` — trace, analyze, report, exit 1 on violations
|
|
148
|
+
- `netaudit analyze --allowlist <yaml> <strace-log>` — offline analysis
|
|
149
|
+
- `--format {text,json}` for machine-readable output
|
|
150
|
+
- `--allowlist` defaults to `netaudit.yaml` in cwd if it exists
|
|
151
|
+
- Exit codes: 0 clean, 1 violations, 2 strace missing
|
|
152
|
+
|
|
153
|
+
**Tests** (`tests/test_cli.py`):
|
|
154
|
+
- Click `CliRunner` tests (mocked runner for `run`, real file for `analyze`)
|
|
155
|
+
- Exit code semantics
|
|
156
|
+
- Integration test: real `netaudit run` invocation
|
|
157
|
+
|
|
158
|
+
**Docs** (`docs/` + `mkdocs.yml`):
|
|
159
|
+
- MkDocs Material: nav, search, code highlighting
|
|
160
|
+
- Pages: index (overview + quickstart), cli-reference, allowlist-dsl, architecture
|
|
161
|
+
- README.md updated with badges, install instructions, quickstart
|
|
162
|
+
|
|
163
|
+
**CI update:** Add `mkdocs build --strict` to lint job.
|
|
164
|
+
|
|
165
|
+
**Exit:** Both CLI commands work. Docs build. Tagged `v0.1.0`.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Phase 4 — pytest Plugin
|
|
170
|
+
|
|
171
|
+
**Goal:** Automatic network auditing during pytest runs.
|
|
172
|
+
|
|
173
|
+
**Session-level** (first commit):
|
|
174
|
+
- `netaudit/integrations/pytest_plugin.py`, registered via `pytest11` entry point
|
|
175
|
+
- `pytest_addoption`: `--netaudit`, `--netaudit-allowlist`
|
|
176
|
+
- `pytest_sessionstart`: start strace tracing the test process
|
|
177
|
+
- `pytest_sessionfinish`: stop, parse, check, report violations, set exit code
|
|
178
|
+
- Allowlist resolution: CLI flag > `[tool.netaudit]` in pyproject.toml > `netaudit.yaml` in cwd
|
|
179
|
+
|
|
180
|
+
**Per-test attribution** (second commit):
|
|
181
|
+
- `pytest_runtest_protocol`: write timestamp markers to sidecar file at test boundaries
|
|
182
|
+
- Correlate ConnectEvents with test items by timestamp range
|
|
183
|
+
- Reporter enriched: violations grouped by test name
|
|
184
|
+
|
|
185
|
+
**Tests:** `pytester`-based tests for both modes.
|
|
186
|
+
|
|
187
|
+
**Docs:** `docs/pytest-plugin.md` — usage, config, examples.
|
|
188
|
+
|
|
189
|
+
**Exit:** `pytest --netaudit` traces and reports. Per-test attribution shows which test caused each violation.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Phase 5 — Release Engineering
|
|
194
|
+
|
|
195
|
+
**Goal:** Automated publishing, Docker image, hardening.
|
|
196
|
+
|
|
197
|
+
- **PyPI workflow** (`.github/workflows/release.yml`): build + publish on GitHub release
|
|
198
|
+
- **Docs deploy** (`.github/workflows/docs.yml`): `mkdocs gh-deploy` on push to main
|
|
199
|
+
- **Dockerfile**: `python:3.12-slim` + strace, `ENTRYPOINT ["netaudit"]`
|
|
200
|
+
- **Docker CI**: build + push to GHCR on release
|
|
201
|
+
- **Hardening**: truncated strace output, binary garbage in paths, long lines, permission errors, signal handling
|
|
202
|
+
- **Docs:** Docker usage page, contributing guide
|
|
203
|
+
|
|
204
|
+
**Exit:** `pip install netaudit` from PyPI, `docker run` works, docs live on GH Pages.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Skills
|
|
209
|
+
|
|
210
|
+
| Skill | Created in | Description |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `/commit` | Phase 0 | Auto-detect conventional commit type from diff (feat/fix/refactor/test/docs/chore), minimal description, GPG-signed, no co-author |
|
|
213
|
+
|
|
214
|
+
Additional skills (`/phase-docs`, `/coverage-check`, `/release-prep`) to be created when the need arises, not upfront.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Verification
|
|
219
|
+
|
|
220
|
+
Each phase verified by:
|
|
221
|
+
1. `ruff check . && ruff format --check . && mypy netaudit/` — lint clean
|
|
222
|
+
2. `pytest --cov=netaudit --cov-fail-under=80` — tests pass with coverage
|
|
223
|
+
3. `pytest -m integration` (Linux only) — real strace tests (from Phase 2)
|
|
224
|
+
4. `mkdocs build --strict` — docs build (from Phase 3)
|
|
225
|
+
5. CI green on push
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Key Files (final state)
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
pyproject.toml
|
|
233
|
+
Makefile
|
|
234
|
+
.gitignore
|
|
235
|
+
mkdocs.yml
|
|
236
|
+
Dockerfile
|
|
237
|
+
netaudit/
|
|
238
|
+
__init__.py
|
|
239
|
+
py.typed
|
|
240
|
+
parser.py
|
|
241
|
+
allowlist.py
|
|
242
|
+
reporter.py
|
|
243
|
+
runner.py
|
|
244
|
+
cli.py
|
|
245
|
+
integrations/
|
|
246
|
+
__init__.py
|
|
247
|
+
pytest_plugin.py
|
|
248
|
+
tests/
|
|
249
|
+
conftest.py
|
|
250
|
+
test_parser.py
|
|
251
|
+
test_allowlist.py
|
|
252
|
+
test_reporter.py
|
|
253
|
+
test_cli.py
|
|
254
|
+
integration/
|
|
255
|
+
conftest.py
|
|
256
|
+
egress_target.py
|
|
257
|
+
test_end_to_end.py
|
|
258
|
+
docs/
|
|
259
|
+
index.md
|
|
260
|
+
architecture.md
|
|
261
|
+
cli-reference.md
|
|
262
|
+
allowlist-dsl.md
|
|
263
|
+
pytest-plugin.md
|
|
264
|
+
docker.md
|
|
265
|
+
.github/workflows/
|
|
266
|
+
ci.yml
|
|
267
|
+
docs.yml
|
|
268
|
+
release.yml
|
|
269
|
+
.claude/commands/
|
|
270
|
+
commit.md
|
|
271
|
+
```
|
netaudit-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
`netaudit` is a Python library and CLI that wraps a process or test suite under `strace`, collects all `connect()` syscalls, filters them against a declarative allowlist, and reports violations. The goal: commit a config file declaring what's allowed, run tests normally, get pass/fail with readable output instead of raw strace noise.
|
|
8
|
+
|
|
9
|
+
`strace` is a **system dependency** — documented, not bundled. No other runtime dependencies. `pytest` is optional (only needed for the plugin).
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Set up venv (Python 3.11+)
|
|
15
|
+
python3.11 -m venv .venv
|
|
16
|
+
.venv/bin/pip install -e ".[dev]"
|
|
17
|
+
|
|
18
|
+
# Run all tests
|
|
19
|
+
.venv/bin/pytest
|
|
20
|
+
|
|
21
|
+
# Run a single test
|
|
22
|
+
.venv/bin/pytest tests/test_parser.py::TestStraceParser::test_parse_af_inet_connect
|
|
23
|
+
|
|
24
|
+
# CLI usage
|
|
25
|
+
.venv/bin/netaudit run --allowlist netaudit.yaml -- <command>
|
|
26
|
+
.venv/bin/netaudit analyze --allowlist netaudit.yaml /tmp/strace.log
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
Two-layer design: a **language-agnostic core** (syscall tracing + analysis) and **framework-specific integrations** that add execution context (e.g. test attribution). The core must not import anything from integrations.
|
|
32
|
+
|
|
33
|
+
### Core (`netaudit/`)
|
|
34
|
+
|
|
35
|
+
| Module | Role |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `runner.py` | `StraceRunner` — spawns strace as a subprocess wrapping a command, or attaches to an existing PID via `attach(pid)` |
|
|
38
|
+
| `parser.py` | `StraceParser` — line-by-line regex parser producing `ConnectEvent` dataclasses |
|
|
39
|
+
| `allowlist.py` | `AllowList` + rule types: `IPv4`, `IPv6`, `UnixSocket`, `Netlink` — loaded from YAML or constructed programmatically |
|
|
40
|
+
| `reporter.py` | `Violation`, `Reporter` — groups events, formats output; enriched with test attribution when markers are available |
|
|
41
|
+
| `cli.py` | CLI entry point (`netaudit run` / `netaudit analyze`) |
|
|
42
|
+
|
|
43
|
+
### `ConnectEvent` (central data type)
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
@dataclass
|
|
47
|
+
class ConnectEvent:
|
|
48
|
+
pid: int
|
|
49
|
+
timestamp: float
|
|
50
|
+
family: str # "AF_INET", "AF_INET6", "AF_UNIX", "AF_NETLINK", ...
|
|
51
|
+
addr: str | None # IP or socket path
|
|
52
|
+
port: int | None
|
|
53
|
+
result: int # 0 = success; negative errno (e.g. -ECONNREFUSED)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Parser edge cases to handle
|
|
57
|
+
|
|
58
|
+
- `EINPROGRESS`: non-blocking connect in flight — not a failure, should still be evaluated against the allowlist
|
|
59
|
+
- Multi-line strace continuations (syscall split across lines)
|
|
60
|
+
- Thread interleavings when strace is run with `-f`
|
|
61
|
+
|
|
62
|
+
### AllowList DSL
|
|
63
|
+
|
|
64
|
+
YAML format committed to the repo:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
version: 1
|
|
68
|
+
allowlist:
|
|
69
|
+
- comment: "GVM Unix socket"
|
|
70
|
+
family: AF_UNIX
|
|
71
|
+
path_prefix: /run/gvmd/
|
|
72
|
+
- comment: "GVM TCP proxy"
|
|
73
|
+
family: AF_INET
|
|
74
|
+
addr: 127.0.0.1
|
|
75
|
+
port: 9393
|
|
76
|
+
- comment: "IPv6 loopback"
|
|
77
|
+
family: AF_INET6
|
|
78
|
+
addr: "::1"
|
|
79
|
+
- comment: "glibc resolver internals"
|
|
80
|
+
family: AF_NETLINK
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`Netlink()` and loopback are reasonable built-in defaults — users opt **out** rather than explicitly allowing them.
|
|
84
|
+
|
|
85
|
+
### pytest integration (`netaudit/integrations/pytest_plugin.py`)
|
|
86
|
+
|
|
87
|
+
Registered via `entry_points` in `pyproject.toml` (not imported directly). Re-execs the pytest process with strace as parent. Emits timestamp markers at session and test boundaries so violations can be attributed to individual test cases. Calls `pytest.fail()` after `pytest_sessionfinish` if violations exist.
|
|
88
|
+
|
|
89
|
+
Activated via `pyproject.toml`:
|
|
90
|
+
```toml
|
|
91
|
+
[tool.netaudit]
|
|
92
|
+
allowlist = "netaudit.yaml"
|
|
93
|
+
enabled = true
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Future integrations
|
|
97
|
+
|
|
98
|
+
Node.js (Jest/Vitest/Mocha) and other runners are explicitly in scope. All integrations follow the same pattern: **emit execution markers → correlate with syscall timestamps → enrich violation reports**. New integrations go in `netaudit/integrations/` and must not require changes to core modules.
|
|
99
|
+
|
|
100
|
+
## Key design constraints
|
|
101
|
+
|
|
102
|
+
- The core is entirely framework-agnostic — no pytest imports outside `integrations/`
|
|
103
|
+
- Exit code is non-zero on any violation (CI-native)
|
|
104
|
+
- The CLI (`netaudit run`) must work with any executable, not just Python test suites
|
|
105
|
+
- Regex is sufficient for parsing strace output — the format is stable and well-documented
|
|
106
|
+
- `mypy --strict` enforced from day one; `py.typed` marker included
|
|
107
|
+
- `ruff` for linting and formatting (line-length=100, select E,F,W,I)
|
|
108
|
+
|
|
109
|
+
## Development plan
|
|
110
|
+
|
|
111
|
+
See [`.instructions/plan.md`](.instructions/plan.md) for the full phased development plan (6 phases, agile approach with CI/CD from Phase 0).
|