daemonaudit 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.
- daemonaudit-0.1.0/.github/workflows/ci.yml +50 -0
- daemonaudit-0.1.0/.gitignore +10 -0
- daemonaudit-0.1.0/AGENTS.md +53 -0
- daemonaudit-0.1.0/BUILD.md +218 -0
- daemonaudit-0.1.0/CLAUDE.md +4 -0
- daemonaudit-0.1.0/LICENSE +21 -0
- daemonaudit-0.1.0/PKG-INFO +113 -0
- daemonaudit-0.1.0/README.md +89 -0
- daemonaudit-0.1.0/assets/.gitkeep +0 -0
- daemonaudit-0.1.0/assets/daemon.svg +13 -0
- daemonaudit-0.1.0/assets/demo-report.svg +282 -0
- daemonaudit-0.1.0/pyproject.toml +42 -0
- daemonaudit-0.1.0/reviews/claude/.gitkeep +0 -0
- daemonaudit-0.1.0/reviews/claude/2026-08-27-evasion-secrets.md +21 -0
- daemonaudit-0.1.0/reviews/claude/2026-08-27-evasion-skills.md +28 -0
- daemonaudit-0.1.0/reviews/claude/2026-08-27-m1-review.md +24 -0
- daemonaudit-0.1.0/reviews/claude/2026-08-27-windows-adapter.md +31 -0
- daemonaudit-0.1.0/reviews/codex/.gitkeep +0 -0
- daemonaudit-0.1.0/reviews/codex/2026-08-27-evasion-secrets.md +122 -0
- daemonaudit-0.1.0/reviews/codex/2026-08-27-evasion-skills.md +126 -0
- daemonaudit-0.1.0/reviews/codex/2026-08-27-m1-review.md +191 -0
- daemonaudit-0.1.0/reviews/codex/2026-08-27-windows-adapter.md +93 -0
- daemonaudit-0.1.0/scripts/demo_home.py +58 -0
- daemonaudit-0.1.0/src/daemonaudit/__init__.py +3 -0
- daemonaudit-0.1.0/src/daemonaudit/__main__.py +4 -0
- daemonaudit-0.1.0/src/daemonaudit/banner.py +13 -0
- daemonaudit-0.1.0/src/daemonaudit/chain/__init__.py +3 -0
- daemonaudit-0.1.0/src/daemonaudit/chain/rules.py +161 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/__init__.py +0 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/_walk.py +63 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/advisories.py +75 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/network.py +149 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/perms.py +245 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/policy.py +371 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/secrets.py +127 -0
- daemonaudit-0.1.0/src/daemonaudit/checks/skills.py +464 -0
- daemonaudit-0.1.0/src/daemonaudit/cli.py +110 -0
- daemonaudit-0.1.0/src/daemonaudit/discover/__init__.py +11 -0
- daemonaudit-0.1.0/src/daemonaudit/discover/hermes.py +104 -0
- daemonaudit-0.1.0/src/daemonaudit/discover/hermes_config.py +155 -0
- daemonaudit-0.1.0/src/daemonaudit/model.py +288 -0
- daemonaudit-0.1.0/src/daemonaudit/platform/__init__.py +3 -0
- daemonaudit-0.1.0/src/daemonaudit/platform/base.py +359 -0
- daemonaudit-0.1.0/src/daemonaudit/probes/__init__.py +0 -0
- daemonaudit-0.1.0/src/daemonaudit/probes/red.py +241 -0
- daemonaudit-0.1.0/src/daemonaudit/redact.py +314 -0
- daemonaudit-0.1.0/src/daemonaudit/registry.py +93 -0
- daemonaudit-0.1.0/src/daemonaudit/report/__init__.py +0 -0
- daemonaudit-0.1.0/src/daemonaudit/report/html.py +125 -0
- daemonaudit-0.1.0/src/daemonaudit/report/json_out.py +11 -0
- daemonaudit-0.1.0/src/daemonaudit/report/terminal.py +121 -0
- daemonaudit-0.1.0/tests/conftest.py +45 -0
- daemonaudit-0.1.0/tests/fixtures/.gitkeep +0 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/lowercase-token.env +2 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/non-secrets.txt +6 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/providers.env +8 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/quoted-backslashes.env +2 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/split-lines.env +3 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/sqlite-null-page.b64 +1 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/unicode-lookalikes.env +2 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/url-and-export.env +4 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/wrapped-values.yaml +3 -0
- daemonaudit-0.1.0/tests/fixtures/evasion/yaml-block.yaml +4 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/alternate-network/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/alternate-network/check.sh +4 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/eval-substitution/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/eval-substitution/install.sh +3 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/frontmatter-block/SKILL.md +11 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/frontmatter-inline-safe/SKILL.md +7 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/frontmatter-metadata/SKILL.md +8 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/injection-obfuscated/SKILL.md +8 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/legit-api-client/SKILL.md +6 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/legit-api-client/client.py +10 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/legit-documentation/SKILL.md +7 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/legit-safe-patterns/SKILL.md +11 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/obfuscated-secret-read/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/obfuscated-secret-read/client.sh +3 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/pipe-line-continuation/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/pipe-line-continuation/install.sh +3 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/pipe-variables/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/pipe-variables/install.sh +4 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/python-c-network/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/python-c-network/run.sh +2 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/python-subprocess/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/python-subprocess/bootstrap.py +6 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/unicode-soft-hyphen/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/fixtures/evasion-skills/vendor-risk/SKILL.md +5 -0
- daemonaudit-0.1.0/tests/test_checks.py +77 -0
- daemonaudit-0.1.0/tests/test_evasion_secrets.py +125 -0
- daemonaudit-0.1.0/tests/test_evasion_skills.py +131 -0
- daemonaudit-0.1.0/tests/test_html.py +35 -0
- daemonaudit-0.1.0/tests/test_m2.py +174 -0
- daemonaudit-0.1.0/tests/test_m3.py +193 -0
- daemonaudit-0.1.0/tests/test_redact.py +37 -0
- daemonaudit-0.1.0/tests/test_review_m1.py +125 -0
- daemonaudit-0.1.0/tests/test_scrub_pairs.py +31 -0
- daemonaudit-0.1.0/tests/test_windows.py +92 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [master]
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
pull_request:
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
strategy:
|
|
10
|
+
fail-fast: false
|
|
11
|
+
matrix:
|
|
12
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
13
|
+
python: ["3.10", "3.12"]
|
|
14
|
+
runs-on: ${{ matrix.os }}
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python }}
|
|
20
|
+
- run: python -m pip install -e ".[dev]"
|
|
21
|
+
- run: python -m pytest -q
|
|
22
|
+
- name: smoke-scan a demo home
|
|
23
|
+
shell: bash
|
|
24
|
+
run: |
|
|
25
|
+
python scripts/demo_home.py demo-hermes
|
|
26
|
+
daemonaudit scan --red --home demo-hermes --json out.json --html out.html || test $? -eq 2
|
|
27
|
+
python -c "import json; d=json.load(open('out.json')); assert d['summary']['attack_paths'] >= 1, d['summary']"
|
|
28
|
+
build:
|
|
29
|
+
needs: test
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: actions/setup-python@v5
|
|
34
|
+
with: { python-version: "3.12" }
|
|
35
|
+
- run: python -m pip install build && python -m build
|
|
36
|
+
- uses: actions/upload-artifact@v4
|
|
37
|
+
with: { name: dist, path: dist/ }
|
|
38
|
+
# Publishes to PyPI via Trusted Publishing (OIDC — no API token stored).
|
|
39
|
+
# One-time setup on pypi.org before the first release can publish: see below.
|
|
40
|
+
publish:
|
|
41
|
+
needs: build
|
|
42
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment: pypi
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write # mint the OIDC token PyPI trusts; no secrets needed
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/download-artifact@v4
|
|
49
|
+
with: { name: dist, path: dist/ }
|
|
50
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# daemonaudit — shared brief for every agent working here
|
|
2
|
+
|
|
3
|
+
This file is read by Codex (AGENTS.md) and by Claude Code (CLAUDE.md points here).
|
|
4
|
+
It holds the invariants. BUILD.md holds the plan and the task queue.
|
|
5
|
+
|
|
6
|
+
## What this is
|
|
7
|
+
A Python CLI that audits self-hosted AI agent daemons (Hermes Agent first; OpenClaw
|
|
8
|
+
and generic MCP configs later) for secrets on disk, exposed ports, weak policy config
|
|
9
|
+
and risky skills, then reports **attack paths and blast radius**, grouped by attacker
|
|
10
|
+
position (remote / injected content / supply-chain / local).
|
|
11
|
+
|
|
12
|
+
## Invariants — never break these
|
|
13
|
+
1. **Read-only.** The scanner never modifies the host. `Remediation.apply()` is v0.2
|
|
14
|
+
and is not wired to anything.
|
|
15
|
+
2. **Zero network egress.** No telemetry, no update checks, no "phone home". Red-team
|
|
16
|
+
probes connect to **localhost only** and must hard-fail on any non-loopback target.
|
|
17
|
+
3. **Secrets are redacted at detection.** `RedactedSecret` has no raw-value field on
|
|
18
|
+
purpose. Evidence strings are redacted. All report output passes through
|
|
19
|
+
`redact.scrub()` as a safety net. A test asserts no fixture secret appears in output.
|
|
20
|
+
4. **No false passes.** A check that cannot run raises `Skipped` → status `skip`.
|
|
21
|
+
An exception → status `error`. Neither ever renders as `pass`.
|
|
22
|
+
5. **Never follow symlinks** when walking the daemon home. A hostile skill can plant one.
|
|
23
|
+
6. **Every Finding** has `why`, `fix`, and (almost always) `verify_cmd`. If you can't
|
|
24
|
+
tell the user how to verify the fix, the finding isn't done.
|
|
25
|
+
7. **Framework layout knowledge lives in `discover/<framework>.py` only.** Checks ask
|
|
26
|
+
the Target for paths; they never hard-code `~/.hermes`.
|
|
27
|
+
8. **Fixtures use obviously fake secrets** (`FAKE` in the body, correct shape). Never
|
|
28
|
+
commit a real key, a real chat ID, or a real phone number — not even in a report.
|
|
29
|
+
|
|
30
|
+
## Layout
|
|
31
|
+
```
|
|
32
|
+
src/daemonaudit/
|
|
33
|
+
model.py Finding / CheckResult / Target / ScanReport / RedactedSecret
|
|
34
|
+
redact.py detection patterns, display(), fingerprint(), scrub()
|
|
35
|
+
registry.py @check decorator, run_all(), Skipped
|
|
36
|
+
platform/ OS abstraction (posix now, windows v0.2); psutil-backed
|
|
37
|
+
discover/ framework adapters (hermes.py)
|
|
38
|
+
checks/ BLUE passive checks — one file per area
|
|
39
|
+
probes/ RED active probes — localhost only (milestone 3)
|
|
40
|
+
chain/ attack-path rules (milestone 3)
|
|
41
|
+
report/ terminal (rich), json, html (milestone 4)
|
|
42
|
+
tests/ pytest; conftest.py builds a fake ~/.hermes with one of everything
|
|
43
|
+
reviews/codex/ Codex writes its reports here (see BUILD.md)
|
|
44
|
+
reviews/claude/ Claude's responses to those reports
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Conventions
|
|
48
|
+
- Python ≥ 3.10, deps: psutil, rich, pyyaml. Don't add more without a reason in BUILD.md.
|
|
49
|
+
- Check ids: `AREA-NNN` (SEC, PERM, NET, POL, SKILL, PROC, RED). Stable forever once shipped.
|
|
50
|
+
- Severity: CRITICAL = remote unauth → code exec or all secrets; HIGH = secrets exposed
|
|
51
|
+
or unauth reachable service; MEDIUM = policy weakness that needs one more hop; LOW = hygiene.
|
|
52
|
+
- Run `pytest` before declaring anything done. `daemonaudit scan` against a real home is
|
|
53
|
+
the smoke test.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# BUILD.md — plan, status, and the Codex task queue
|
|
2
|
+
|
|
3
|
+
Two agents work on this repo. **Claude Code** builds `src/` during milestones 1–2.
|
|
4
|
+
**Codex** reviews, writes adversarial fixtures, and (milestone 3) builds the Windows
|
|
5
|
+
platform adapter. Both read `AGENTS.md` first. Humans read this file to see where things are.
|
|
6
|
+
|
|
7
|
+
## Milestones
|
|
8
|
+
|
|
9
|
+
| # | Scope | Status |
|
|
10
|
+
|---|-------|--------|
|
|
11
|
+
| 1 | Skeleton, model, redaction, Hermes discovery, checks SEC-001 / PERM-001..003, terminal + JSON report, tests | **done 2026-08-27** (pending Codex review) |
|
|
12
|
+
| 2 | NET-001/002 listeners + unix sockets; POL-001..010 (yolo/exec-ask, approvals, sandbox, allow-all users, API server, webhooks/dashboard, debug leaks, SSRF/tirith/project plugins, env passthrough, MCP literal secrets); SKILL-001 (8 categories, bundled-skill detection); ADV-001 (local update cache + acked advisories) | **done 2026-08-27** (pending Codex C3) |
|
|
13
|
+
| 3 | RED-001 unauth HTTP probe (localhost gate, hard-fail otherwise), RED-002 exec-time process env, RED-003 vault blast radius; `chain/rules.py` (9 rules, tag-based, foothold floor) → attack paths with kill-hop + per-kind blast radius table; `info` status; exit codes ignore INFO | **done 2026-08-27** (pending Codex C4) |
|
|
14
|
+
| 4 | `--html` self-contained report; mascot SVG + `assets/demo-report.svg` screenshot from `scripts/demo_home.py`; LICENSE (MIT); GitHub Actions CI on Linux/macOS/Windows × py3.10/3.12 with a demo smoke-scan + build; README; version 0.1.0 + tag | **done 2026-08-27** |
|
|
15
|
+
| v0.2 | Windows ACL adapter, canary-injection probe, OpenClaw + generic MCP adapters, local-Ollama semantic skill review, guided remediation with rollback, `dir_fd`-relative walking for hostile trees, scrub-only pattern set broader than finding patterns | |
|
|
16
|
+
|
|
17
|
+
## Threat model (what every check maps to)
|
|
18
|
+
- **remote** — unauthenticated, on the network: exposed gateway/dashboard/API, no auth, default tokens
|
|
19
|
+
- **content** — controls something the agent reads: prompt injection → tool use with no allowlist/sandbox
|
|
20
|
+
- **supply-chain** — ships a skill / MCP server / plugin: install scripts, env passthrough, tool descriptions that instruct exfil
|
|
21
|
+
- **local** — another user/process on the host: file perms, secrets in transcripts/backups/process env
|
|
22
|
+
|
|
23
|
+
## How Codex and Claude hand work to each other
|
|
24
|
+
|
|
25
|
+
### Where reports go
|
|
26
|
+
- Codex writes every report to `reviews/codex/YYYY-MM-DD-<task-slug>.md`.
|
|
27
|
+
- Claude answers in `reviews/claude/YYYY-MM-DD-<task-slug>.md` with one line per item:
|
|
28
|
+
`fixed (commit/diff ref)` · `rejected — <why>` · `deferred to M<n>`.
|
|
29
|
+
- A report is "closed" when every item has a disposition. Don't reopen closed reports; write a new one.
|
|
30
|
+
|
|
31
|
+
### Rules for Codex tasks
|
|
32
|
+
- **Review tasks: do not edit `src/`.** Report only. Suggested patches go in the report as diff blocks.
|
|
33
|
+
- **Fixture tasks:** add files only under `tests/fixtures/` and new `tests/test_evasion_*.py`. Fake secrets only (see AGENTS.md §8). A fixture that the current scanner *misses* is the goal — mark those tests `xfail` with a reason so the suite stays green until Claude patches the detector.
|
|
34
|
+
- **Code tasks (M3 Windows adapter):** branch `codex/<slug>`, touch only the files the task lists, run `pytest` before finishing.
|
|
35
|
+
- Never run the scanner against a real home and paste output into a report without checking it for secrets/chat IDs/phone numbers first. Prefer the pytest fixture home.
|
|
36
|
+
|
|
37
|
+
### Report template
|
|
38
|
+
```markdown
|
|
39
|
+
# <task slug> — Codex report, <date>
|
|
40
|
+
|
|
41
|
+
## Scope
|
|
42
|
+
What was reviewed / produced, with file paths.
|
|
43
|
+
|
|
44
|
+
## Findings
|
|
45
|
+
### <n>. <title> [severity: blocker | should-fix | nit]
|
|
46
|
+
- **Where:** path:line
|
|
47
|
+
- **What:** one paragraph
|
|
48
|
+
- **Why it matters:** one paragraph
|
|
49
|
+
- **Suggested fix:** diff block or description
|
|
50
|
+
|
|
51
|
+
## Things I checked that are fine
|
|
52
|
+
Bullet list. This matters as much as the findings — it tells Claude what not to re-verify.
|
|
53
|
+
|
|
54
|
+
## Open questions for Claude
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Lessons from the first real run (2026-08-27, WSL2 box)
|
|
58
|
+
Codex: read these before C1/C2 — they are the known state, don't re-report them.
|
|
59
|
+
- **Two false positives shipped and were fixed the same night.** A placeholder
|
|
60
|
+
(`your-password-here`) in a world-readable backup was reported as a HIGH credential leak,
|
|
61
|
+
and an English hyphenated word inside `state.db` matched the `sk-` regex. Fixes: left
|
|
62
|
+
boundary lookbehind on prefix patterns, `_looks_random()` (digits + mixed case) on
|
|
63
|
+
non-structural kinds, broader placeholder substrings. **This is exactly the class of bug C2
|
|
64
|
+
should hunt** — the detector now leans toward false negatives, and that trade-off is untested.
|
|
65
|
+
- `verify_cmd` uses GNU `stat -c`; macOS needs `stat -f`. Not yet platform-aware.
|
|
66
|
+
- PERM-001 emits one finding per *directory* for logs/sessions/etc. (was one per file —
|
|
67
|
+
8 near-identical panels). Top-level files still get individual findings.
|
|
68
|
+
- Real result on a fresh, default Hermes 0.20.6 install: 0 high, 4 medium, 3 low. The
|
|
69
|
+
mediums are all Hermes writing `state.db`, logs and history with the default umask
|
|
70
|
+
(644) despite its docs saying 0600. Worth an upstream issue once we're confident.
|
|
71
|
+
- Dev env: no system `venv`/`pip` on this box; `uv` is at `~/.local/bin`. `uv venv --python 3.12 .venv && uv pip install -p .venv/bin/python -e '.[dev]'`.
|
|
72
|
+
|
|
73
|
+
## Decisions recorded from C1 (2026-08-27)
|
|
74
|
+
- Result statuses: `pass · fail · skip · off · error · incomplete`. `off` = deliberate opt-out
|
|
75
|
+
(red probes without `--red`) and does not make a scan incomplete.
|
|
76
|
+
- Exit codes: 0 clean+complete · 1 findings · 2 high/critical · 3 no target · 4 no findings but
|
|
77
|
+
incomplete · 5 tool error. Precedence 2 > 1 > 4 > 0. Source of truth: `model.EXIT_CODE_HELP`.
|
|
78
|
+
- Checks return `CheckOutput(findings, coverage_notes)`; anything not inspected is a note, never silence.
|
|
79
|
+
- `--home` may be a symlink: resolved once at discovery (`meta.home_as_given` kept). Nothing below
|
|
80
|
+
the root is ever followed; reads use `O_NOFOLLOW` + `fstat`. Full `dir_fd` walking → v0.2.
|
|
81
|
+
- Framework layout lives in `model.Layout`, filled by `discover/<fw>.py`. Checks never import adapter constants.
|
|
82
|
+
- Shell snippets in findings: paths via `platform.q()` (shlex), `stat` via `Platform.stat_cmd()` (Darwin-aware).
|
|
83
|
+
|
|
84
|
+
## Decisions recorded from C2 (2026-08-27)
|
|
85
|
+
- Detection runs over bounded derived streams (`shell`, `nfkc`, `yaml-fold`, `nulls`, `base64`, `hex`).
|
|
86
|
+
**Derived streams only yield structural/provider kinds; `generic-credential` must match as written.**
|
|
87
|
+
- Two randomness heuristics: prefixed (strict) vs assignment-anchored generic (relaxed) + a
|
|
88
|
+
non-secret-name denylist. Change one without the other and you re-open a known bug.
|
|
89
|
+
- `scrub()` is deliberately broader than findings: replaces hit carriers and any 40+-char opaque blob.
|
|
90
|
+
- The evasion matrix is enforced (no xfails left). New evasions go in as `xfail`; promote when fixed.
|
|
91
|
+
- `Hit.via` is user-visible evidence; `Hit.carrier` never leaves memory.
|
|
92
|
+
|
|
93
|
+
## Lessons from M2 (2026-08-27)
|
|
94
|
+
- **SKILL-001's first cut cried wolf.** Against the 82 vendor skills it flagged `tmux send-keys … 'JWT tokens'` as
|
|
95
|
+
exfiltration, `cat ~/.ssh/id_ed25519.pub` as a secret read, a review rubric saying "exfiltration" as injection,
|
|
96
|
+
and `<!-- ascii-guard-ignore -->` as a hidden instruction. Fixes: object+destination required for "send … token … to",
|
|
97
|
+
`id_*\b(?!\.pub)` (the `\b` matters — backtracking defeats a bare lookahead), imperative-to-the-agent required in comments,
|
|
98
|
+
credential-*named* env reads only. Then: **bundled detection** — a flagged file byte-identical to `hermes-agent/skills/…`
|
|
99
|
+
is labelled `(bundled)` and the category is downgraded one notch (never for invisible-unicode / vault requests / scripts piping to shell).
|
|
100
|
+
- `hermes-agent` and `bin` are excluded **only at the home root** now (`Layout.exclude_root_dirs`); a skill named
|
|
101
|
+
`hermes-agent` was being swallowed. Any-depth exclusions are just venv/node_modules/__pycache__/.git.
|
|
102
|
+
- Settings come from `discover/hermes_config.py`: config.yaml + .env + (fallback) the audit shell env, with source
|
|
103
|
+
tracking. Findings show env var *names* and policy values, never credential values.
|
|
104
|
+
- NET-001 attributes sockets to the gateway **and its children** (the node sidecar is a child). psutil `net_connections`
|
|
105
|
+
needs root on macOS → NotSupported → `skip`, honestly.
|
|
106
|
+
- ADV-001 reads Hermes's own `.update_check` cache. Zero egress preserved.
|
|
107
|
+
- Real box after M2: 0 high · 5 medium · 9 low · complete · 1.2 s. The mediums: unsandboxed local backend, world-readable
|
|
108
|
+
state/logs/history (Hermes umask), and nothing else.
|
|
109
|
+
|
|
110
|
+
## Decisions recorded from C3 (2026-08-27)
|
|
111
|
+
- SKILL-001 normalises before matching: scripts (continuations, quote-splitting, `+` concat, simple `VAR=` substitution),
|
|
112
|
+
docs (NFKC, homoglyphs, default-ignorables incl. soft hyphen, link text, bounded base64), Python via stdlib `ast` taint.
|
|
113
|
+
- Credentials are two-tier: `MASTER_ENV` names (Hermes's own provider/platform keys) are risk; scoped names are inventory.
|
|
114
|
+
- Frontmatter is parsed as YAML; `metadata:` declarations are parsed but tagged non-runtime and graded down (Hermes reads top level only).
|
|
115
|
+
- `DEFENSIVE` vocabulary suppresses injection matches on lines that discuss attacks — a known, documented evasion trade-off.
|
|
116
|
+
- Binary files (by extension or NUL sniff) are never scanned by doc heuristics. Scrub's blob pattern excludes `/` so paths survive.
|
|
117
|
+
- Every C3 fixture row is enforced; the corpus is SKILL-001's regression floor.
|
|
118
|
+
|
|
119
|
+
## Lessons from M3 (2026-08-27)
|
|
120
|
+
- `/proc/<pid>/environ` is the **exec-time** environment. Hermes loads `.env` after start, so RED-002 came back
|
|
121
|
+
clean on a box whose daemon plainly holds keys. The check now says exactly what it proved; RED-003 (read the vault
|
|
122
|
+
as a same-user process) is the real local blast-radius number.
|
|
123
|
+
- INFO-only findings used to mark a check `fail` and would have exited 1 on a clean box. New status `info`;
|
|
124
|
+
`ScanReport.actionable` excludes INFO from exit codes and counts.
|
|
125
|
+
- First chain run produced two "attack paths" on the default install, both footed on the vendor's own `curl | bash`
|
|
126
|
+
install docs (bundled, LOW). Rule now: hop 1 ≥ MEDIUM, intermediates ≥ LOW, final hop any. Default install → 0 paths.
|
|
127
|
+
- The localhost gate is one function (`probes/red._assert_local`) and it is tested against public IPs and hostnames.
|
|
128
|
+
Every probe goes through `_http_get()` which calls it first. Keep it that way.
|
|
129
|
+
- Real box with `--red`: 0 high · 5 medium · 10 low · 0 paths · vault = 5 credentials / 3 kinds (JWTs in auth.json).
|
|
130
|
+
|
|
131
|
+
## Releasing to PyPI (Trusted Publishing)
|
|
132
|
+
The `publish` job in `.github/workflows/ci.yml` runs on any `v*` tag and publishes via OIDC —
|
|
133
|
+
no API token is stored anywhere. One-time setup on pypi.org, then every `vX.Y.Z` tag publishes.
|
|
134
|
+
|
|
135
|
+
**One-time (user, on pypi.org, logged in):** Account → *Publishing* → *Add a pending publisher*:
|
|
136
|
+
- PyPI Project Name: `daemonaudit`
|
|
137
|
+
- Owner: `TLLABS-dev` Repository: `daemonaudit`
|
|
138
|
+
- Workflow name: `ci.yml` Environment: `pypi`
|
|
139
|
+
|
|
140
|
+
Then in the GitHub repo: Settings → Environments → **New environment** named `pypi` (no secrets needed).
|
|
141
|
+
To publish v0.1.0 after that: re-push the tag so CI re-runs with the publish job present —
|
|
142
|
+
`git push origin :refs/tags/v0.1.0 && git push origin v0.1.0` (delete + repush). Future releases:
|
|
143
|
+
bump `version` in pyproject.toml + `__init__.py`, tag `vX.Y.Z`, push the tag.
|
|
144
|
+
|
|
145
|
+
Token fallback (if Trusted Publishing is ever a problem): add a `PYPI_API_TOKEN` repo secret and
|
|
146
|
+
give the publish step `with: { password: ${{ secrets.PYPI_API_TOKEN }} }`. Trusted Publishing is preferred.
|
|
147
|
+
|
|
148
|
+
## Lessons from M4 (2026-08-27)
|
|
149
|
+
- **Process attribution was too loose.** `find_processes("hermes_cli")` substring-matches any command
|
|
150
|
+
line mentioning the string — including daemonaudit's own shell wrapper. Discovery now requires a
|
|
151
|
+
*python interpreter running hermes_cli* (`GATEWAY_RE`) AND that the process belong to the home being
|
|
152
|
+
scanned (path in cmdline, or matching `HERMES_HOME`). Scanning a demo/backup home no longer probes the
|
|
153
|
+
real daemon. Test fixtures were unrealistic (fake gateway cmdlines lacked the venv path); fixed.
|
|
154
|
+
- Screenshot + demo home use only FAKE credentials (`scripts/demo_home.py`); the screenshot is rendered
|
|
155
|
+
from a home under `$HOME` and deleted, so no scratch path leaks into the committed SVG.
|
|
156
|
+
- HTML report is one file: inlined CSS, no `<script>`, no external asset, light/dark via prefers-color-scheme,
|
|
157
|
+
everything scrubbed + HTML-escaped. Test asserts no raw secret and no `http(s)://` in the output.
|
|
158
|
+
- PyPI publish is wired but commented in CI — needs the project created + Trusted Publisher. `uvx --from git+…`
|
|
159
|
+
works today.
|
|
160
|
+
|
|
161
|
+
## Lessons from C4 native verification (2026-08-27)
|
|
162
|
+
- **There is no Hermes on the Windows side of TLlabs.** No `%USERPROFILE%\.hermes`, no `HERMES_HOME`, no process.
|
|
163
|
+
The "PowerShell" Hermes is the WSL install reached through Windows Terminal. `daemonaudit scan` on native
|
|
164
|
+
Windows correctly exits 3. Real installs to audit: WSL2 and the Mac. The Windows box's job is the adapter.
|
|
165
|
+
- Windows Python defaults to cp1252 for `read_text()/write_text()`. Every test touching a non-ASCII fixture
|
|
166
|
+
must pass `encoding="utf-8"`. The scanner itself reads bytes and is unaffected.
|
|
167
|
+
- PowerShell non-terminating errors exit 0. Any generated PowerShell verify command needs `-ErrorAction Stop`.
|
|
168
|
+
- WSL can drive the Windows side (`powershell.exe` from bash); a fresh WSL session is needed to see PATH
|
|
169
|
+
changes made on Windows after the session started — use full interpreter paths instead.
|
|
170
|
+
- Follow-up: the `posix_only` markers now skip PERM/SEC tests on Windows even though `posix_modes=True`;
|
|
171
|
+
port those fixtures to `icacls` so the checks are exercised natively (v0.2, with the richer principal model).
|
|
172
|
+
|
|
173
|
+
## Codex task queue
|
|
174
|
+
|
|
175
|
+
Take the top unclaimed task. Mark it `claimed <date>` here, then `done <date> → reviews/codex/<file>` when the report is written.
|
|
176
|
+
|
|
177
|
+
### C1 — Milestone 1 review `[closed 2026-08-27 → reviews/codex/2026-08-27-m1-review.md · answered in reviews/claude/2026-08-27-m1-review.md]`
|
|
178
|
+
Review everything under `src/daemonaudit/` and `tests/` against the AGENTS.md invariants. Specifically:
|
|
179
|
+
1. **Redaction end-to-end.** Can a raw secret reach stdout, the JSON file, a traceback, or a `rich` panel title? Trace `Finding.evidence`, `title`, `why`, `CheckResult.note` (tracebacks!), and `to_json()`. Try to construct a Finding that leaks.
|
|
180
|
+
2. **False passes.** Any code path where a check returns `[]` because of an error it swallowed (look at every `except OSError` / `except NotSupported` in `checks/`). Each one should be judged: is silently skipping that *file* acceptable, or should the whole check SKIP?
|
|
181
|
+
3. **Symlink / traversal safety.** `checks/_walk.py` and `platform/base.py`. Can a symlink inside the home make the scanner read or report a path outside it?
|
|
182
|
+
4. **Regex quality in `redact.py`.** False positives you'd expect on real config (e.g. `generic-credential` on `HERMES_SESSION_KEY=` style non-secrets, base64 blobs), and false negatives for common providers (OpenRouter, Google, Discord, Azure, Slack app tokens, JWTs, `Bearer …`).
|
|
183
|
+
5. **macOS.** Anything here that behaves differently on Darwin (psutil `net_connections` needs root on macOS; `os.lstat` fine; `stat -c` in `verify_cmd` is GNU-only — macOS is `stat -f '%A'`). Propose how `verify_cmd` should be platform-aware.
|
|
184
|
+
6. **Exit codes / CLI ergonomics.** Anything that would surprise someone wiring this into cron or CI.
|
|
185
|
+
Output: `reviews/codex/YYYY-MM-DD-m1-review.md`. Do not edit `src/`.
|
|
186
|
+
|
|
187
|
+
### C2 — Evasion fixtures for the secret scanner `[closed 2026-08-27 → reviews/codex/2026-08-27-evasion-secrets.md · answered in reviews/claude/2026-08-27-evasion-secrets.md — all 10 rows now enforced]`
|
|
188
|
+
Your own C1 item #6 is the brief: the detector now leans toward false negatives (`_looks_random()` rejects lowercase-only tokens with <4 digits; `generic-credential` is keyword-context only; no Azure/Google-OAuth/Discord-bot coverage tests). Pin all of that. Write `tests/fixtures/evasion/` + `tests/test_evasion_secrets.py`: a set of files that contain credential-shaped secrets the current `redact.find_secrets()` **misses** or **mis-classifies** — split across lines, base64/hex-wrapped, in YAML block scalars, in sqlite pages with interleaved nulls, URL-embedded (`https://user:token@host`), in `.env` with `export`, quoted with backslashes, unicode look-alikes. Also files that should produce **no** hit (docs with example keys marked as examples, `${VAR}` references, `op://` refs). Every missed case is an `xfail` test with a one-line reason. Report: `reviews/codex/YYYY-MM-DD-evasion-secrets.md` summarising the detection matrix.
|
|
189
|
+
|
|
190
|
+
### C3 — Evasion fixtures for skill heuristics `[closed 2026-08-27 → reviews/codex/2026-08-27-evasion-skills.md · answered in reviews/claude/2026-08-27-evasion-skills.md — all 15 rows enforced]` (M2 shipped SKILL-001 — go)
|
|
191
|
+
`src/daemonaudit/checks/skills.py`. It is regex-only by design (v0.1); your job is to show where regex
|
|
192
|
+
is not enough and pin it. Two directions, both matter:
|
|
193
|
+
1. **Evasions** (false negatives): fixtures under `tests/fixtures/evasion-skills/<skill>/` + `tests/test_evasion_skills.py`,
|
|
194
|
+
`xfail` per row like C2. Ideas: `curl … | sh` split across lines / variables / `$(…)` / `eval` / python `subprocess`;
|
|
195
|
+
network calls via `python -c`, `nc`, `openssl s_client`, DNS exfil; secret reads via `cat $HOME/."env"`, `find ~ -name '*.env'`,
|
|
196
|
+
`env | grep`, `printenv`; injection phrasing with homoglyphs, markdown link text, split words, base64 in SKILL.md;
|
|
197
|
+
frontmatter tricks (`required_environment_variables` as YAML block list, quoted, or in a `metadata:` subtree);
|
|
198
|
+
`required_credential_files` pointing at the vault via `../`.
|
|
199
|
+
2. **False positives** (the harder problem — a default Hermes install has 82 vendor skills and the tool must not cry wolf):
|
|
200
|
+
build fixtures from *legitimate* patterns you'd expect in real skills — API clients reading their own key, install
|
|
201
|
+
docs, review rubrics that mention "exfiltration", `id_*.pub` public keys, HTML comment markers — and assert **no**
|
|
202
|
+
finding. Note `_is_bundled()` downgrades vendor-identical files; test that a modified vendor file is NOT downgraded.
|
|
203
|
+
Report: `reviews/codex/YYYY-MM-DD-evasion-skills.md` with a detection matrix like C2. No `src/` edits.
|
|
204
|
+
Same idea against the skills scanner: `SKILL.md` and scripts that hide `curl | sh`, network calls, secret reads, and invisible-Unicode instructions in ways a regex misses.
|
|
205
|
+
|
|
206
|
+
### C4 — Windows platform adapter `[done 2026-08-27 — natively verified: 64 passed / 17 skipped on Windows 3.12.10]`
|
|
207
|
+
`src/daemonaudit/platform/base.py` → new `WindowsPlatform` (replace the stub). Interface is fixed; report anything it can't express rather than changing it.
|
|
208
|
+
Scope, in order:
|
|
209
|
+
1. `file_mode()` semantics on ACLs: `FileMode.other_readable/other_writable/group_*` should mean "a principal other than the owner, SYSTEM and Administrators has that right". Use `ctypes`/`win32security` only if stdlib can't — prefer stdlib (`subprocess icacls` parsing is acceptable as a first cut if documented). `posix_modes = True` once this works, so PERM-* checks run.
|
|
210
|
+
2. `read_nofollow()`: open with `FILE_FLAG_OPEN_REPARSE_POINT` semantics or reject reparse points — never follow a junction/symlink at the final component.
|
|
211
|
+
3. `stat_cmd()`: a PowerShell one-liner printing the ACL (`(Get-Acl <path>).AccessToString`).
|
|
212
|
+
4. `listening_sockets()` / `process_env()` / `children()`: verify psutil behaves; document what needs elevation.
|
|
213
|
+
5. Hermes home on Windows is `%USERPROFILE%\.hermes` — confirm `discover/hermes.py` finds it (`Path.home()` should).
|
|
214
|
+
Tests: `tests/test_windows.py`, skipped unless `sys.platform == "win32"`. Run the full suite on the Windows box and paste the summary line into the report. Branch `codex/windows-adapter`. Report: `reviews/codex/YYYY-MM-DD-windows-adapter.md`.
|
|
215
|
+
|
|
216
|
+
## Claude's queue (for the record)
|
|
217
|
+
- M2 checks, in the order listed in the milestone table; `NET-001` listening sockets first because it's the only remote-position check and the report is lopsided without it.
|
|
218
|
+
- Answer C1 in `reviews/claude/` before starting M2 if the report is in by then.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Read AGENTS.md (invariants) and BUILD.md (plan + task queue) before doing anything here.
|
|
2
|
+
Claude Code is the primary builder of src/ during milestones 1–2; Codex reviews and writes
|
|
3
|
+
adversarial fixtures. Check reviews/codex/ for new reports at the start of each session and
|
|
4
|
+
answer them in reviews/claude/.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: daemonaudit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Red/blue security audit for self-hosted AI agent daemons. Finds the secrets, the open doors, and the blast radius.
|
|
5
|
+
Project-URL: Homepage, https://github.com/TLLABS-dev/daemonaudit
|
|
6
|
+
Author: TL
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai-agent,audit,hermes,mcp,openclaw,security
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: psutil>=5.9
|
|
19
|
+
Requires-Dist: pyyaml>=6
|
|
20
|
+
Requires-Dist: rich>=13
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
<img src="assets/daemon.svg" align="right" width="120" alt="daemonaudit mascot">
|
|
26
|
+
|
|
27
|
+
# daemonaudit
|
|
28
|
+
|
|
29
|
+
> Who can hurt your AI agent — and how badly.
|
|
30
|
+
|
|
31
|
+
`daemonaudit` is a red/blue security audit for self-hosted AI agent daemons. It finds the
|
|
32
|
+
secrets, the open doors and the weak policies on the machine your agent runs on, then tells
|
|
33
|
+
you the **attack paths** and the **blast radius**: what an attacker gets, from where, and
|
|
34
|
+
which single fix kills the whole chain.
|
|
35
|
+
|
|
36
|
+
Supports **Hermes Agent** today. OpenClaw and generic MCP configs are on the roadmap.
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
*(above: `daemonaudit scan --red` against a deliberately-broken demo home — build your own with `python scripts/demo_home.py`)*
|
|
41
|
+
|
|
42
|
+
## Why not just the built-in audit?
|
|
43
|
+
Framework auditors ask *"is my config right?"*. `daemonaudit` asks *"what does an attacker
|
|
44
|
+
standing here actually get?"* — and it looks at the **host**, not just the config: backups,
|
|
45
|
+
transcripts, sqlite state, process environments, listening sockets, and the skills you installed.
|
|
46
|
+
Then it **chains** the findings: an exposed port *plus* an unsandboxed backend *plus* keys in the
|
|
47
|
+
environment is one attack path, and it names the one hop whose fix breaks it.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
```bash
|
|
51
|
+
uvx --from git+https://github.com/TLLABS-dev/daemonaudit daemonaudit scan # zero-install run
|
|
52
|
+
# or
|
|
53
|
+
pipx install git+https://github.com/TLLABS-dev/daemonaudit
|
|
54
|
+
```
|
|
55
|
+
From a clone:
|
|
56
|
+
```bash
|
|
57
|
+
python -m pip install -e '.[dev]'
|
|
58
|
+
daemonaudit scan
|
|
59
|
+
```
|
|
60
|
+
Requires Python ≥ 3.10. Dependencies: `psutil`, `rich`, `pyyaml`. Linux, macOS and Windows.
|
|
61
|
+
|
|
62
|
+
## Use
|
|
63
|
+
```bash
|
|
64
|
+
daemonaudit scan # passive audit (safe anywhere): secrets, perms, policy, skills, listeners
|
|
65
|
+
daemonaudit scan --red # + active probes against THIS host, attack paths, blast radius
|
|
66
|
+
daemonaudit scan --html report.html # a self-contained HTML report (also --json)
|
|
67
|
+
daemonaudit scan --home /path/to/.hermes
|
|
68
|
+
daemonaudit checks # list every check
|
|
69
|
+
```
|
|
70
|
+
`--red` adds three probes that only ever touch **localhost**: it connects to the daemon's own
|
|
71
|
+
listeners to see which answer without a password, reads the daemon's process environment, and
|
|
72
|
+
reads the vault the way any process running as you could — to measure the real local blast radius.
|
|
73
|
+
|
|
74
|
+
## What it checks (v0.1, Hermes)
|
|
75
|
+
- **Secrets** outside the vault — config, backups, transcripts, sqlite state — with encoded/obfuscated variants
|
|
76
|
+
- **Permissions** — world-readable vault/state, backups looser than their originals, writable sockets
|
|
77
|
+
- **Policy** — yolo/approval bypasses, unsandboxed host execution, allow-all users, exposed/unauthenticated API server, unverified webhooks, secret-leaking debug flags, SSRF guard, credentials forwarded into tool shells, literal secrets in MCP config
|
|
78
|
+
- **Skills** — `curl | sh` (incl. split/`eval`/Python-subprocess forms), exfiltration shapes, prompt-injection and invisible-Unicode in `SKILL.md`, frontmatter that asks for master keys or the vault; vendor-shipped skills are recognised and graded down
|
|
79
|
+
- **Freshness** — updates behind and dismissed advisories, from the daemon's own local cache
|
|
80
|
+
- **Red probes** — unauthenticated HTTP, process-environment secrets, vault blast radius
|
|
81
|
+
|
|
82
|
+
Each finding says **why it matters**, the exact **fix**, and a command to **verify** it.
|
|
83
|
+
|
|
84
|
+
## Principles
|
|
85
|
+
- **Read-only.** It never changes your system.
|
|
86
|
+
- **Zero network egress.** Nothing leaves the box. The active probes connect to localhost only, and refuse any target that isn't this host.
|
|
87
|
+
- **Redacted by construction.** The report can show you `sk-ant-…4f2a`; it cannot show you the key. Nothing that leaves the process — terminal, JSON, HTML — contains a raw credential.
|
|
88
|
+
- **No false passes.** A check that can't run says *skipped*, never *ok*. A scan that couldn't complete exits non-zero.
|
|
89
|
+
|
|
90
|
+
## Exit codes (for cron / CI)
|
|
91
|
+
| code | meaning |
|
|
92
|
+
|---|---|
|
|
93
|
+
| 0 | clean, and every check completed |
|
|
94
|
+
| 1 | findings (low/medium) |
|
|
95
|
+
| 2 | high or critical findings |
|
|
96
|
+
| 3 | no supported daemon found (use `--home`) |
|
|
97
|
+
| 4 | no findings, but a check was skipped/errored/incomplete — **not** a clean bill |
|
|
98
|
+
| 5 | the tool itself failed (`--debug` for a scrubbed traceback) |
|
|
99
|
+
|
|
100
|
+
Precedence 2 > 1 > 4 > 0. INFO-only findings don't affect the exit code.
|
|
101
|
+
|
|
102
|
+
## Roadmap
|
|
103
|
+
- **v0.1** — Hermes: secrets, permissions, policy, skills, exposed services, localhost red probes, attack-path report. Linux · macOS · Windows.
|
|
104
|
+
- **v0.2** — OpenClaw + generic MCP adapters, canary-injection probe, local-LLM (Ollama) semantic skill review, guided remediation with rollback, richer Windows ACL model, continuous monitoring / drift alerts.
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
`pytest` runs the suite (Linux/macOS/Windows in CI). The design invariants live in
|
|
108
|
+
[`AGENTS.md`](AGENTS.md); the plan and history in [`BUILD.md`](BUILD.md). Built with a two-model
|
|
109
|
+
workflow — one model builds, another reviews and writes adversarial fixtures; the review
|
|
110
|
+
rounds are under [`reviews/`](reviews/).
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<img src="assets/daemon.svg" align="right" width="120" alt="daemonaudit mascot">
|
|
2
|
+
|
|
3
|
+
# daemonaudit
|
|
4
|
+
|
|
5
|
+
> Who can hurt your AI agent — and how badly.
|
|
6
|
+
|
|
7
|
+
`daemonaudit` is a red/blue security audit for self-hosted AI agent daemons. It finds the
|
|
8
|
+
secrets, the open doors and the weak policies on the machine your agent runs on, then tells
|
|
9
|
+
you the **attack paths** and the **blast radius**: what an attacker gets, from where, and
|
|
10
|
+
which single fix kills the whole chain.
|
|
11
|
+
|
|
12
|
+
Supports **Hermes Agent** today. OpenClaw and generic MCP configs are on the roadmap.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
*(above: `daemonaudit scan --red` against a deliberately-broken demo home — build your own with `python scripts/demo_home.py`)*
|
|
17
|
+
|
|
18
|
+
## Why not just the built-in audit?
|
|
19
|
+
Framework auditors ask *"is my config right?"*. `daemonaudit` asks *"what does an attacker
|
|
20
|
+
standing here actually get?"* — and it looks at the **host**, not just the config: backups,
|
|
21
|
+
transcripts, sqlite state, process environments, listening sockets, and the skills you installed.
|
|
22
|
+
Then it **chains** the findings: an exposed port *plus* an unsandboxed backend *plus* keys in the
|
|
23
|
+
environment is one attack path, and it names the one hop whose fix breaks it.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
```bash
|
|
27
|
+
uvx --from git+https://github.com/TLLABS-dev/daemonaudit daemonaudit scan # zero-install run
|
|
28
|
+
# or
|
|
29
|
+
pipx install git+https://github.com/TLLABS-dev/daemonaudit
|
|
30
|
+
```
|
|
31
|
+
From a clone:
|
|
32
|
+
```bash
|
|
33
|
+
python -m pip install -e '.[dev]'
|
|
34
|
+
daemonaudit scan
|
|
35
|
+
```
|
|
36
|
+
Requires Python ≥ 3.10. Dependencies: `psutil`, `rich`, `pyyaml`. Linux, macOS and Windows.
|
|
37
|
+
|
|
38
|
+
## Use
|
|
39
|
+
```bash
|
|
40
|
+
daemonaudit scan # passive audit (safe anywhere): secrets, perms, policy, skills, listeners
|
|
41
|
+
daemonaudit scan --red # + active probes against THIS host, attack paths, blast radius
|
|
42
|
+
daemonaudit scan --html report.html # a self-contained HTML report (also --json)
|
|
43
|
+
daemonaudit scan --home /path/to/.hermes
|
|
44
|
+
daemonaudit checks # list every check
|
|
45
|
+
```
|
|
46
|
+
`--red` adds three probes that only ever touch **localhost**: it connects to the daemon's own
|
|
47
|
+
listeners to see which answer without a password, reads the daemon's process environment, and
|
|
48
|
+
reads the vault the way any process running as you could — to measure the real local blast radius.
|
|
49
|
+
|
|
50
|
+
## What it checks (v0.1, Hermes)
|
|
51
|
+
- **Secrets** outside the vault — config, backups, transcripts, sqlite state — with encoded/obfuscated variants
|
|
52
|
+
- **Permissions** — world-readable vault/state, backups looser than their originals, writable sockets
|
|
53
|
+
- **Policy** — yolo/approval bypasses, unsandboxed host execution, allow-all users, exposed/unauthenticated API server, unverified webhooks, secret-leaking debug flags, SSRF guard, credentials forwarded into tool shells, literal secrets in MCP config
|
|
54
|
+
- **Skills** — `curl | sh` (incl. split/`eval`/Python-subprocess forms), exfiltration shapes, prompt-injection and invisible-Unicode in `SKILL.md`, frontmatter that asks for master keys or the vault; vendor-shipped skills are recognised and graded down
|
|
55
|
+
- **Freshness** — updates behind and dismissed advisories, from the daemon's own local cache
|
|
56
|
+
- **Red probes** — unauthenticated HTTP, process-environment secrets, vault blast radius
|
|
57
|
+
|
|
58
|
+
Each finding says **why it matters**, the exact **fix**, and a command to **verify** it.
|
|
59
|
+
|
|
60
|
+
## Principles
|
|
61
|
+
- **Read-only.** It never changes your system.
|
|
62
|
+
- **Zero network egress.** Nothing leaves the box. The active probes connect to localhost only, and refuse any target that isn't this host.
|
|
63
|
+
- **Redacted by construction.** The report can show you `sk-ant-…4f2a`; it cannot show you the key. Nothing that leaves the process — terminal, JSON, HTML — contains a raw credential.
|
|
64
|
+
- **No false passes.** A check that can't run says *skipped*, never *ok*. A scan that couldn't complete exits non-zero.
|
|
65
|
+
|
|
66
|
+
## Exit codes (for cron / CI)
|
|
67
|
+
| code | meaning |
|
|
68
|
+
|---|---|
|
|
69
|
+
| 0 | clean, and every check completed |
|
|
70
|
+
| 1 | findings (low/medium) |
|
|
71
|
+
| 2 | high or critical findings |
|
|
72
|
+
| 3 | no supported daemon found (use `--home`) |
|
|
73
|
+
| 4 | no findings, but a check was skipped/errored/incomplete — **not** a clean bill |
|
|
74
|
+
| 5 | the tool itself failed (`--debug` for a scrubbed traceback) |
|
|
75
|
+
|
|
76
|
+
Precedence 2 > 1 > 4 > 0. INFO-only findings don't affect the exit code.
|
|
77
|
+
|
|
78
|
+
## Roadmap
|
|
79
|
+
- **v0.1** — Hermes: secrets, permissions, policy, skills, exposed services, localhost red probes, attack-path report. Linux · macOS · Windows.
|
|
80
|
+
- **v0.2** — OpenClaw + generic MCP adapters, canary-injection probe, local-LLM (Ollama) semantic skill review, guided remediation with rollback, richer Windows ACL model, continuous monitoring / drift alerts.
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
`pytest` runs the suite (Linux/macOS/Windows in CI). The design invariants live in
|
|
84
|
+
[`AGENTS.md`](AGENTS.md); the plan and history in [`BUILD.md`](BUILD.md). Built with a two-model
|
|
85
|
+
workflow — one model builds, another reviews and writes adversarial fixtures; the review
|
|
86
|
+
rounds are under [`reviews/`](reviews/).
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
MIT — see [LICENSE](LICENSE).
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160" viewBox="0 0 64 64" role="img" aria-label="daemonaudit mascot: a small purple demon">
|
|
2
|
+
<path d="M14 22 L8 6 L22 16 Z" fill="#c0392b"/>
|
|
3
|
+
<path d="M50 22 L56 6 L42 16 Z" fill="#c0392b"/>
|
|
4
|
+
<circle cx="32" cy="36" r="22" fill="#7d3c98"/>
|
|
5
|
+
<circle cx="24" cy="32" r="4.5" fill="#fff"/>
|
|
6
|
+
<circle cx="40" cy="32" r="4.5" fill="#fff"/>
|
|
7
|
+
<circle cx="25" cy="33" r="2.2" fill="#111"/>
|
|
8
|
+
<circle cx="41" cy="33" r="2.2" fill="#111"/>
|
|
9
|
+
<path d="M21 44 Q32 53 43 44" stroke="#fff" stroke-width="3" fill="none" stroke-linecap="round"/>
|
|
10
|
+
<path d="M27 45 l2 4 M37 45 l-2 4" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/>
|
|
11
|
+
<path d="M52 44 q10 4 4 14 q-2 3 -6 1" stroke="#7d3c98" stroke-width="3" fill="none" stroke-linecap="round"/>
|
|
12
|
+
<path d="M54 62 l4 -4 l2 5 Z" fill="#c0392b"/>
|
|
13
|
+
</svg>
|