agent-harness-aibom 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.
Files changed (32) hide show
  1. agent_harness_aibom-0.1.0/LICENSE +21 -0
  2. agent_harness_aibom-0.1.0/PKG-INFO +124 -0
  3. agent_harness_aibom-0.1.0/README.md +110 -0
  4. agent_harness_aibom-0.1.0/pyproject.toml +26 -0
  5. agent_harness_aibom-0.1.0/setup.cfg +4 -0
  6. agent_harness_aibom-0.1.0/src/agent_harness_aibom.egg-info/PKG-INFO +124 -0
  7. agent_harness_aibom-0.1.0/src/agent_harness_aibom.egg-info/SOURCES.txt +30 -0
  8. agent_harness_aibom-0.1.0/src/agent_harness_aibom.egg-info/dependency_links.txt +1 -0
  9. agent_harness_aibom-0.1.0/src/agent_harness_aibom.egg-info/entry_points.txt +2 -0
  10. agent_harness_aibom-0.1.0/src/agent_harness_aibom.egg-info/requires.txt +4 -0
  11. agent_harness_aibom-0.1.0/src/agent_harness_aibom.egg-info/top_level.txt +1 -0
  12. agent_harness_aibom-0.1.0/src/harness_aibom/__init__.py +10 -0
  13. agent_harness_aibom-0.1.0/src/harness_aibom/cli.py +109 -0
  14. agent_harness_aibom-0.1.0/src/harness_aibom/collectors/__init__.py +2 -0
  15. agent_harness_aibom-0.1.0/src/harness_aibom/collectors/base.py +33 -0
  16. agent_harness_aibom-0.1.0/src/harness_aibom/collectors/hermes.py +206 -0
  17. agent_harness_aibom-0.1.0/src/harness_aibom/collectors/mcp.py +36 -0
  18. agent_harness_aibom-0.1.0/src/harness_aibom/collectors/ollama.py +50 -0
  19. agent_harness_aibom-0.1.0/src/harness_aibom/collectors/openclaw.py +148 -0
  20. agent_harness_aibom-0.1.0/src/harness_aibom/collectors/secrets.py +35 -0
  21. agent_harness_aibom-0.1.0/src/harness_aibom/cyclonedx.py +84 -0
  22. agent_harness_aibom-0.1.0/src/harness_aibom/diff.py +50 -0
  23. agent_harness_aibom-0.1.0/src/harness_aibom/fingerprint.py +32 -0
  24. agent_harness_aibom-0.1.0/src/harness_aibom/model.py +110 -0
  25. agent_harness_aibom-0.1.0/src/harness_aibom/validate.py +44 -0
  26. agent_harness_aibom-0.1.0/tests/test_cli.py +43 -0
  27. agent_harness_aibom-0.1.0/tests/test_cyclonedx.py +44 -0
  28. agent_harness_aibom-0.1.0/tests/test_diff.py +42 -0
  29. agent_harness_aibom-0.1.0/tests/test_fingerprint.py +18 -0
  30. agent_harness_aibom-0.1.0/tests/test_hermes_collector.py +132 -0
  31. agent_harness_aibom-0.1.0/tests/test_openclaw_collector.py +91 -0
  32. agent_harness_aibom-0.1.0/tests/test_validate.py +46 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the agent-harness-aibom contributors
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,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-harness-aibom
3
+ Version: 0.1.0
4
+ Summary: AIBOM generator for AI agent harnesses (Hermes, OpenClaw) and their model/skill/MCP/hook surface
5
+ Author: Practical DevSecOps
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: PyYAML>=6.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8.0; extra == "dev"
13
+ Dynamic: license-file
14
+
15
+ # agent-harness-aibom
16
+
17
+ An AIBOM (AI Bill of Materials) generator for AI agent harnesses —
18
+ currently Hermes and [OpenClaw](https://docs.openclaw.ai) — that emits standard CycloneDX 1.6
19
+ JSON extended with harness-specific fields (skill fingerprints, MCP server
20
+ TLS/auth posture, hook approval state, model provenance). See
21
+ [`SPEC.md`](SPEC.md) for the full data model and its rationale.
22
+
23
+ This is the "ground truth" layer for a broader exercise: scan a real
24
+ harness with this tool, scan the same harness with a general-purpose
25
+ scanner (e.g. Cisco AI BOM), and diff what each one saw.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install -e ".[dev]" # from a checkout, for development
31
+ ```
32
+
33
+ (Not yet published to PyPI.)
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ # Auto-detect and scan whatever's installed under $HOME
39
+ harness-aibom scan --runtime auto -o aibom.json
40
+
41
+ # Scan a specific runtime explicitly
42
+ harness-aibom scan --runtime hermes -o hermes-aibom.json
43
+ harness-aibom scan --runtime openclaw -o openclaw-aibom.json
44
+
45
+ # Check a document's shape
46
+ harness-aibom validate aibom.json
47
+
48
+ # Compare two scans -- e.g. before/after a suspected skill compromise
49
+ harness-aibom diff before.json after.json
50
+ ```
51
+
52
+ `scan` runs entirely against the local filesystem and local subprocesses/
53
+ HTTP calls (`hermes`/`openclaw` CLIs, Ollama's `/api/tags`). To scan a
54
+ remote lab VM, install the package there (or SSH in and run it) — there's
55
+ no built-in remote transport yet.
56
+
57
+ Missing pieces are never fatal: if `hermes` isn't on `PATH`, or Ollama
58
+ isn't reachable, the scan still completes and prints a `warning[...]` line
59
+ to stderr explaining what it skipped, so students can see exactly what
60
+ partial information looks like.
61
+
62
+ ## Example output
63
+
64
+ [`examples/hermes-aibom.example.json`](examples/hermes-aibom.example.json)
65
+ and [`examples/openclaw-aibom.example.json`](examples/openclaw-aibom.example.json)
66
+ were generated by running `scan` against the test fixtures in
67
+ `tests/fixtures/` (a synthetic `config.yaml`/`openclaw.json`, no live
68
+ `hermes`/`openclaw`/`ollama` on this machine) — a realistic example of
69
+ graceful degradation when the runtime binary isn't present.
70
+
71
+ ## Project layout
72
+
73
+ ```
74
+ src/harness_aibom/
75
+ ├── model.py # Component / HarnessDocument -- the in-memory data model
76
+ ├── cyclonedx.py # model.py -> CycloneDX 1.6 JSON
77
+ ├── fingerprint.py # sha256 helpers
78
+ ├── validate.py # structural checks on a harness-aibom document
79
+ ├── diff.py # before/after comparison
80
+ ├── cli.py # `harness-aibom` entrypoint
81
+ └── collectors/
82
+ ├── base.py # Collector ABC
83
+ ├── ollama.py # shared: model discovery via Ollama's HTTP API
84
+ ├── mcp.py # shared: MCP server extraction from a config dict
85
+ ├── secrets.py # shared: secrets-surface discovery (paths/perms only)
86
+ ├── hermes.py # Hermes collector
87
+ └── openclaw.py # OpenClaw collector
88
+ ```
89
+
90
+ ## CI and publishing
91
+
92
+ - `.github/workflows/ci.yml` runs the test suite and a CLI smoke test on
93
+ every push and pull request, on Python 3.10, 3.11, and 3.12.
94
+ - `.github/workflows/publish.yml` builds and publishes the package to PyPI
95
+ when you publish a GitHub Release. It uses PyPI Trusted Publishing, so no
96
+ password lives in this repo. One-time setup before your first release:
97
+ 1. Go to <https://pypi.org/manage/account/publishing/>.
98
+ 2. Add a new trusted publisher for project `agent-harness-aibom`.
99
+ 3. Set the owner and repo name to match this GitHub repo.
100
+ 4. Set the workflow name to `publish.yml`.
101
+ 5. Set the environment name to `pypi`.
102
+
103
+ ## Testing
104
+
105
+ ```bash
106
+ pytest -q
107
+ ```
108
+
109
+ Collector tests run entirely against fixtures under `tests/fixtures/`
110
+ (`hermes_home/`, `openclaw_home/`) with injected fake `run`/`fetch`
111
+ functions — no real `hermes`/`openclaw`/`ollama` needed to run the suite.
112
+
113
+ ## Roadmap
114
+
115
+ This is v0.1 — the spec plus two working collectors. Natural next steps,
116
+ roughly in order:
117
+
118
+ 1. Harden hook parsing against a live Hermes box (see `SPEC.md` §5).
119
+ 2. Point this at the actual CAASP lab VMs and fix whatever the real output
120
+ shapes get wrong.
121
+ 3. Scan the same harness with Cisco AI BOM and diff the two documents —
122
+ the gap-analysis exercise this package exists to support.
123
+ 4. cosign signing/provenance for the emitted AIBOM.
124
+ 5. Package for PyPI.
@@ -0,0 +1,110 @@
1
+ # agent-harness-aibom
2
+
3
+ An AIBOM (AI Bill of Materials) generator for AI agent harnesses —
4
+ currently Hermes and [OpenClaw](https://docs.openclaw.ai) — that emits standard CycloneDX 1.6
5
+ JSON extended with harness-specific fields (skill fingerprints, MCP server
6
+ TLS/auth posture, hook approval state, model provenance). See
7
+ [`SPEC.md`](SPEC.md) for the full data model and its rationale.
8
+
9
+ This is the "ground truth" layer for a broader exercise: scan a real
10
+ harness with this tool, scan the same harness with a general-purpose
11
+ scanner (e.g. Cisco AI BOM), and diff what each one saw.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install -e ".[dev]" # from a checkout, for development
17
+ ```
18
+
19
+ (Not yet published to PyPI.)
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ # Auto-detect and scan whatever's installed under $HOME
25
+ harness-aibom scan --runtime auto -o aibom.json
26
+
27
+ # Scan a specific runtime explicitly
28
+ harness-aibom scan --runtime hermes -o hermes-aibom.json
29
+ harness-aibom scan --runtime openclaw -o openclaw-aibom.json
30
+
31
+ # Check a document's shape
32
+ harness-aibom validate aibom.json
33
+
34
+ # Compare two scans -- e.g. before/after a suspected skill compromise
35
+ harness-aibom diff before.json after.json
36
+ ```
37
+
38
+ `scan` runs entirely against the local filesystem and local subprocesses/
39
+ HTTP calls (`hermes`/`openclaw` CLIs, Ollama's `/api/tags`). To scan a
40
+ remote lab VM, install the package there (or SSH in and run it) — there's
41
+ no built-in remote transport yet.
42
+
43
+ Missing pieces are never fatal: if `hermes` isn't on `PATH`, or Ollama
44
+ isn't reachable, the scan still completes and prints a `warning[...]` line
45
+ to stderr explaining what it skipped, so students can see exactly what
46
+ partial information looks like.
47
+
48
+ ## Example output
49
+
50
+ [`examples/hermes-aibom.example.json`](examples/hermes-aibom.example.json)
51
+ and [`examples/openclaw-aibom.example.json`](examples/openclaw-aibom.example.json)
52
+ were generated by running `scan` against the test fixtures in
53
+ `tests/fixtures/` (a synthetic `config.yaml`/`openclaw.json`, no live
54
+ `hermes`/`openclaw`/`ollama` on this machine) — a realistic example of
55
+ graceful degradation when the runtime binary isn't present.
56
+
57
+ ## Project layout
58
+
59
+ ```
60
+ src/harness_aibom/
61
+ ├── model.py # Component / HarnessDocument -- the in-memory data model
62
+ ├── cyclonedx.py # model.py -> CycloneDX 1.6 JSON
63
+ ├── fingerprint.py # sha256 helpers
64
+ ├── validate.py # structural checks on a harness-aibom document
65
+ ├── diff.py # before/after comparison
66
+ ├── cli.py # `harness-aibom` entrypoint
67
+ └── collectors/
68
+ ├── base.py # Collector ABC
69
+ ├── ollama.py # shared: model discovery via Ollama's HTTP API
70
+ ├── mcp.py # shared: MCP server extraction from a config dict
71
+ ├── secrets.py # shared: secrets-surface discovery (paths/perms only)
72
+ ├── hermes.py # Hermes collector
73
+ └── openclaw.py # OpenClaw collector
74
+ ```
75
+
76
+ ## CI and publishing
77
+
78
+ - `.github/workflows/ci.yml` runs the test suite and a CLI smoke test on
79
+ every push and pull request, on Python 3.10, 3.11, and 3.12.
80
+ - `.github/workflows/publish.yml` builds and publishes the package to PyPI
81
+ when you publish a GitHub Release. It uses PyPI Trusted Publishing, so no
82
+ password lives in this repo. One-time setup before your first release:
83
+ 1. Go to <https://pypi.org/manage/account/publishing/>.
84
+ 2. Add a new trusted publisher for project `agent-harness-aibom`.
85
+ 3. Set the owner and repo name to match this GitHub repo.
86
+ 4. Set the workflow name to `publish.yml`.
87
+ 5. Set the environment name to `pypi`.
88
+
89
+ ## Testing
90
+
91
+ ```bash
92
+ pytest -q
93
+ ```
94
+
95
+ Collector tests run entirely against fixtures under `tests/fixtures/`
96
+ (`hermes_home/`, `openclaw_home/`) with injected fake `run`/`fetch`
97
+ functions — no real `hermes`/`openclaw`/`ollama` needed to run the suite.
98
+
99
+ ## Roadmap
100
+
101
+ This is v0.1 — the spec plus two working collectors. Natural next steps,
102
+ roughly in order:
103
+
104
+ 1. Harden hook parsing against a live Hermes box (see `SPEC.md` §5).
105
+ 2. Point this at the actual CAASP lab VMs and fix whatever the real output
106
+ shapes get wrong.
107
+ 3. Scan the same harness with Cisco AI BOM and diff the two documents —
108
+ the gap-analysis exercise this package exists to support.
109
+ 4. cosign signing/provenance for the emitted AIBOM.
110
+ 5. Package for PyPI.
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "agent-harness-aibom"
3
+ version = "0.1.0"
4
+ description = "AIBOM generator for AI agent harnesses (Hermes, OpenClaw) and their model/skill/MCP/hook surface"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Practical DevSecOps" }]
9
+ dependencies = [
10
+ "PyYAML>=6.0",
11
+ ]
12
+
13
+ [project.optional-dependencies]
14
+ dev = [
15
+ "pytest>=8.0",
16
+ ]
17
+
18
+ [project.scripts]
19
+ harness-aibom = "harness_aibom.cli:main"
20
+
21
+ [build-system]
22
+ requires = ["setuptools>=68", "wheel"]
23
+ build-backend = "setuptools.build_meta"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-harness-aibom
3
+ Version: 0.1.0
4
+ Summary: AIBOM generator for AI agent harnesses (Hermes, OpenClaw) and their model/skill/MCP/hook surface
5
+ Author: Practical DevSecOps
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: PyYAML>=6.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8.0; extra == "dev"
13
+ Dynamic: license-file
14
+
15
+ # agent-harness-aibom
16
+
17
+ An AIBOM (AI Bill of Materials) generator for AI agent harnesses —
18
+ currently Hermes and [OpenClaw](https://docs.openclaw.ai) — that emits standard CycloneDX 1.6
19
+ JSON extended with harness-specific fields (skill fingerprints, MCP server
20
+ TLS/auth posture, hook approval state, model provenance). See
21
+ [`SPEC.md`](SPEC.md) for the full data model and its rationale.
22
+
23
+ This is the "ground truth" layer for a broader exercise: scan a real
24
+ harness with this tool, scan the same harness with a general-purpose
25
+ scanner (e.g. Cisco AI BOM), and diff what each one saw.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install -e ".[dev]" # from a checkout, for development
31
+ ```
32
+
33
+ (Not yet published to PyPI.)
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ # Auto-detect and scan whatever's installed under $HOME
39
+ harness-aibom scan --runtime auto -o aibom.json
40
+
41
+ # Scan a specific runtime explicitly
42
+ harness-aibom scan --runtime hermes -o hermes-aibom.json
43
+ harness-aibom scan --runtime openclaw -o openclaw-aibom.json
44
+
45
+ # Check a document's shape
46
+ harness-aibom validate aibom.json
47
+
48
+ # Compare two scans -- e.g. before/after a suspected skill compromise
49
+ harness-aibom diff before.json after.json
50
+ ```
51
+
52
+ `scan` runs entirely against the local filesystem and local subprocesses/
53
+ HTTP calls (`hermes`/`openclaw` CLIs, Ollama's `/api/tags`). To scan a
54
+ remote lab VM, install the package there (or SSH in and run it) — there's
55
+ no built-in remote transport yet.
56
+
57
+ Missing pieces are never fatal: if `hermes` isn't on `PATH`, or Ollama
58
+ isn't reachable, the scan still completes and prints a `warning[...]` line
59
+ to stderr explaining what it skipped, so students can see exactly what
60
+ partial information looks like.
61
+
62
+ ## Example output
63
+
64
+ [`examples/hermes-aibom.example.json`](examples/hermes-aibom.example.json)
65
+ and [`examples/openclaw-aibom.example.json`](examples/openclaw-aibom.example.json)
66
+ were generated by running `scan` against the test fixtures in
67
+ `tests/fixtures/` (a synthetic `config.yaml`/`openclaw.json`, no live
68
+ `hermes`/`openclaw`/`ollama` on this machine) — a realistic example of
69
+ graceful degradation when the runtime binary isn't present.
70
+
71
+ ## Project layout
72
+
73
+ ```
74
+ src/harness_aibom/
75
+ ├── model.py # Component / HarnessDocument -- the in-memory data model
76
+ ├── cyclonedx.py # model.py -> CycloneDX 1.6 JSON
77
+ ├── fingerprint.py # sha256 helpers
78
+ ├── validate.py # structural checks on a harness-aibom document
79
+ ├── diff.py # before/after comparison
80
+ ├── cli.py # `harness-aibom` entrypoint
81
+ └── collectors/
82
+ ├── base.py # Collector ABC
83
+ ├── ollama.py # shared: model discovery via Ollama's HTTP API
84
+ ├── mcp.py # shared: MCP server extraction from a config dict
85
+ ├── secrets.py # shared: secrets-surface discovery (paths/perms only)
86
+ ├── hermes.py # Hermes collector
87
+ └── openclaw.py # OpenClaw collector
88
+ ```
89
+
90
+ ## CI and publishing
91
+
92
+ - `.github/workflows/ci.yml` runs the test suite and a CLI smoke test on
93
+ every push and pull request, on Python 3.10, 3.11, and 3.12.
94
+ - `.github/workflows/publish.yml` builds and publishes the package to PyPI
95
+ when you publish a GitHub Release. It uses PyPI Trusted Publishing, so no
96
+ password lives in this repo. One-time setup before your first release:
97
+ 1. Go to <https://pypi.org/manage/account/publishing/>.
98
+ 2. Add a new trusted publisher for project `agent-harness-aibom`.
99
+ 3. Set the owner and repo name to match this GitHub repo.
100
+ 4. Set the workflow name to `publish.yml`.
101
+ 5. Set the environment name to `pypi`.
102
+
103
+ ## Testing
104
+
105
+ ```bash
106
+ pytest -q
107
+ ```
108
+
109
+ Collector tests run entirely against fixtures under `tests/fixtures/`
110
+ (`hermes_home/`, `openclaw_home/`) with injected fake `run`/`fetch`
111
+ functions — no real `hermes`/`openclaw`/`ollama` needed to run the suite.
112
+
113
+ ## Roadmap
114
+
115
+ This is v0.1 — the spec plus two working collectors. Natural next steps,
116
+ roughly in order:
117
+
118
+ 1. Harden hook parsing against a live Hermes box (see `SPEC.md` §5).
119
+ 2. Point this at the actual CAASP lab VMs and fix whatever the real output
120
+ shapes get wrong.
121
+ 3. Scan the same harness with Cisco AI BOM and diff the two documents —
122
+ the gap-analysis exercise this package exists to support.
123
+ 4. cosign signing/provenance for the emitted AIBOM.
124
+ 5. Package for PyPI.
@@ -0,0 +1,30 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/agent_harness_aibom.egg-info/PKG-INFO
5
+ src/agent_harness_aibom.egg-info/SOURCES.txt
6
+ src/agent_harness_aibom.egg-info/dependency_links.txt
7
+ src/agent_harness_aibom.egg-info/entry_points.txt
8
+ src/agent_harness_aibom.egg-info/requires.txt
9
+ src/agent_harness_aibom.egg-info/top_level.txt
10
+ src/harness_aibom/__init__.py
11
+ src/harness_aibom/cli.py
12
+ src/harness_aibom/cyclonedx.py
13
+ src/harness_aibom/diff.py
14
+ src/harness_aibom/fingerprint.py
15
+ src/harness_aibom/model.py
16
+ src/harness_aibom/validate.py
17
+ src/harness_aibom/collectors/__init__.py
18
+ src/harness_aibom/collectors/base.py
19
+ src/harness_aibom/collectors/hermes.py
20
+ src/harness_aibom/collectors/mcp.py
21
+ src/harness_aibom/collectors/ollama.py
22
+ src/harness_aibom/collectors/openclaw.py
23
+ src/harness_aibom/collectors/secrets.py
24
+ tests/test_cli.py
25
+ tests/test_cyclonedx.py
26
+ tests/test_diff.py
27
+ tests/test_fingerprint.py
28
+ tests/test_hermes_collector.py
29
+ tests/test_openclaw_collector.py
30
+ tests/test_validate.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ harness-aibom = harness_aibom.cli:main
@@ -0,0 +1,4 @@
1
+ PyYAML>=6.0
2
+
3
+ [dev]
4
+ pytest>=8.0
@@ -0,0 +1,10 @@
1
+ """agent-harness-aibom: an AIBOM generator for AI agent harnesses.
2
+
3
+ This package answers a narrower question than a general-purpose AI BOM
4
+ scanner: what exactly constitutes a *deployed agent harness* -- its runtime
5
+ binary, the model endpoint(s) it talks to, the configuration and skills it
6
+ loads, the MCP servers and hooks it can reach, and the secrets surface
7
+ around all of that. See SPEC.md at the repo root for the full data model.
8
+ """
9
+
10
+ __version__ = "0.1.0"
@@ -0,0 +1,109 @@
1
+ """`harness-aibom` command-line entrypoint: scan / validate / diff."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from . import __version__
11
+ from .collectors.hermes import HermesCollector
12
+ from .collectors.openclaw import OpenClawCollector
13
+ from .cyclonedx import current_hostname, to_cyclonedx
14
+ from .diff import diff_documents
15
+ from .model import HarnessDocument
16
+ from .validate import validate_document
17
+
18
+ COLLECTORS = {
19
+ "hermes": HermesCollector,
20
+ "openclaw": OpenClawCollector,
21
+ }
22
+
23
+
24
+ def _run_scan(args: argparse.Namespace) -> int:
25
+ home = Path(args.home).expanduser() if args.home else Path.home()
26
+
27
+ if args.runtime == "auto":
28
+ candidates = [cls(home=home) for cls in COLLECTORS.values()]
29
+ active = [c for c in candidates if c.is_present()]
30
+ if not active:
31
+ print(f"no supported runtime found under {home}", file=sys.stderr)
32
+ return 1
33
+ else:
34
+ active = [COLLECTORS[args.runtime](home=home)]
35
+
36
+ for collector in active:
37
+ doc = HarnessDocument(
38
+ harness_name=f"{collector.runtime_kind}@{current_hostname()}",
39
+ runtime_kind=collector.runtime_kind,
40
+ hostname=current_hostname(),
41
+ )
42
+ collector.collect(doc)
43
+ for w in doc.warnings:
44
+ print(f"warning[{collector.runtime_kind}]: {w}", file=sys.stderr)
45
+
46
+ bom = to_cyclonedx(doc)
47
+ text = json.dumps(bom, indent=2 if args.pretty else None)
48
+
49
+ if args.output:
50
+ out_path = Path(args.output)
51
+ if len(active) > 1:
52
+ out_path = out_path.with_name(f"{collector.runtime_kind}-{out_path.name}")
53
+ out_path.write_text(text + "\n")
54
+ print(f"wrote {out_path}")
55
+ else:
56
+ print(text)
57
+
58
+ return 0
59
+
60
+
61
+ def _run_validate(args: argparse.Namespace) -> int:
62
+ data = json.loads(Path(args.file).read_text())
63
+ errors = validate_document(data)
64
+ if errors:
65
+ for e in errors:
66
+ print(f"error: {e}", file=sys.stderr)
67
+ return 1
68
+ print(f"{args.file}: valid")
69
+ return 0
70
+
71
+
72
+ def _run_diff(args: argparse.Namespace) -> int:
73
+ before = json.loads(Path(args.before).read_text())
74
+ after = json.loads(Path(args.after).read_text())
75
+ print(json.dumps(diff_documents(before, after), indent=2))
76
+ return 0
77
+
78
+
79
+ def build_parser() -> argparse.ArgumentParser:
80
+ parser = argparse.ArgumentParser(prog="harness-aibom")
81
+ parser.add_argument("--version", action="version", version=__version__)
82
+ sub = parser.add_subparsers(dest="command", required=True)
83
+
84
+ scan = sub.add_parser("scan", help="scan a running harness and emit a CycloneDX AIBOM")
85
+ scan.add_argument("--runtime", choices=["auto", *COLLECTORS], default="auto")
86
+ scan.add_argument("--home", help="home directory to scan under (default: current user's)")
87
+ scan.add_argument("--output", "-o", help="write to this file instead of stdout")
88
+ scan.add_argument("--pretty", action="store_true", default=True, help="pretty-print JSON (default: on)")
89
+ scan.set_defaults(func=_run_scan)
90
+
91
+ validate = sub.add_parser("validate", help="check a harness-aibom JSON document's shape")
92
+ validate.add_argument("file")
93
+ validate.set_defaults(func=_run_validate)
94
+
95
+ diff = sub.add_parser("diff", help="compare two harness-aibom documents, e.g. before/after a suspected compromise")
96
+ diff.add_argument("before")
97
+ diff.add_argument("after")
98
+ diff.set_defaults(func=_run_diff)
99
+
100
+ return parser
101
+
102
+
103
+ def main(argv: list[str] | None = None) -> int:
104
+ args = build_parser().parse_args(argv)
105
+ return args.func(args)
106
+
107
+
108
+ if __name__ == "__main__":
109
+ raise SystemExit(main())
@@ -0,0 +1,2 @@
1
+ """Runtime-specific collectors. Each module here implements one Collector
2
+ subclass (see base.py) for one agent harness product (Hermes, OpenClaw)."""
@@ -0,0 +1,33 @@
1
+ """Collector base class shared by every runtime-specific collector."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from pathlib import Path
7
+
8
+ from ..model import HarnessDocument
9
+
10
+
11
+ class Collector(ABC):
12
+ """One collector = one runtime kind (hermes, openclaw, ...).
13
+
14
+ A collector never raises just because the runtime isn't installed --
15
+ it records that via `doc.warn()` and adds nothing. It should only raise
16
+ for genuine programmer errors (e.g. a broken test fixture), not for
17
+ "this piece is missing on this machine".
18
+ """
19
+
20
+ runtime_kind: str
21
+
22
+ def __init__(self, home: Path | None = None):
23
+ self.home = home or Path.home()
24
+
25
+ @abstractmethod
26
+ def is_present(self) -> bool:
27
+ """Cheap, side-effect-free check: does this runtime look installed
28
+ under self.home? Used by `scan --runtime auto`."""
29
+
30
+ @abstractmethod
31
+ def collect(self, doc: HarnessDocument) -> None:
32
+ """Populate doc with every component/relationship this collector
33
+ can find. Must not raise for missing-but-optional pieces."""