Skip to content

Commit 6940fe2

Browse files
Merge remote-tracking branch 'origin/main' into docs/advisory-coverage-progress
2 parents f9581b1 + 5bfd4ca commit 6940fe2

6 files changed

Lines changed: 553 additions & 0 deletions

File tree

‎.github/workflows/tests.yml‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,26 @@ jobs:
3535
run: python -m unittest -v
3636
- name: Check CLI help
3737
run: python node2shell_check.py --help
38+
39+
optional-signed-rules:
40+
name: Optional TUF Python ${{ matrix.python }} on ${{ matrix.os }}
41+
runs-on: ${{ matrix.os }}
42+
timeout-minutes: 10
43+
strategy:
44+
fail-fast: false
45+
matrix:
46+
os: [ubuntu-latest, windows-latest, macos-latest]
47+
python: ['3.10', '3.14']
48+
steps:
49+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
50+
with:
51+
persist-credentials: false
52+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
53+
with:
54+
python-version: ${{ matrix.python }}
55+
- name: Install isolated optional verifier dependencies
56+
run: python -m pip install -r tools/signed_rules/requirements.txt
57+
- name: Test offline synthetic trust lifecycle
58+
run: python -m unittest discover -s tools/signed_rules -v
59+
- name: Check optional CLI help
60+
run: python tools/signed_rules/verify_bundle.py --help

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ Dual licensed: [AGPL-3.0](LICENSE), including commercial use that meets its term
101101
An interruption after a complete report is published preserves that report and exits 130.
102102
CSV rejects NUL characters before writing a report.
103103

104+
Optional release tooling: [offline TUF bundle verification](tools/signed_rules/README.md)
105+
uses separate optional dependencies and an explicitly provisioned public trust root.
106+
It never changes the standalone checker or automatically applies a rule update.
107+
104108
Optional family metadata tools: [Node capability and build-context collection](docs/NODE_METADATA.md).
105109
These separate formats do not change the Node2Shell inventory schema or infer
106110
application exposure from available APIs, lockfile declarations or image tags.

‎tools/signed_rules/README.md‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Optional signed-rule bundle verification
2+
3+
This development tool verifies an **existing local TUF bundle** with maintained
4+
[python-tuf 7.0.1](https://theupdateframework.readthedocs.io/en/latest/api/tuf.ngclient.updater.html).
5+
It performs no network requests, executes no rules, and never changes the
6+
checker's embedded snapshot. It produces an authenticated JSON artifact for
7+
subsequent source review and a release build. Authentication does not establish
8+
advisory accuracy, provenance of a reported runtime, or local exploitability.
9+
The checker remains one file with no third-party dependencies.
10+
11+
## Trust recommendation and provisioning
12+
13+
CompleteTech should provision an offline Ed25519 root with a 2-of-3 threshold,
14+
held by independent custodians, and separate targets, snapshot and timestamp
15+
roles. Keep routine release keys separate from root custody. Production private
16+
keys must never enter this repository, CI artifacts, reports, or support logs.
17+
Ship the **public** root through an independently authenticated release channel
18+
and record its SHA-256 in the release receipt. Do not trust a root merely because
19+
it arrived beside an update. No production root, private key or automatic feed
20+
is configured here. Tests generate ephemeral synthetic keys in memory.
21+
22+
Root replacement must satisfy both old and new root thresholds; rotate release
23+
keys through authorized root metadata. A root compromise requires an independent
24+
trust recovery procedure, rather than accepting a replacement supplied by the
25+
untrusted bundle. The [TUF specification](https://theupdateframework.github.io/specification/latest/)
26+
defines expiration, signature thresholds and rollback checks.
27+
28+
## Explicit offline use
29+
30+
Use an isolated environment; these optional crypto dependencies are justified
31+
because implementing signature/trust logic ourselves would be unsafe.
32+
33+
```sh
34+
python -m venv .venv-tuf
35+
.venv-tuf/bin/python -m pip install -r tools/signed_rules/requirements.txt
36+
.venv-tuf/bin/python tools/signed_rules/verify_bundle.py --help
37+
.venv-tuf/bin/python tools/signed_rules/verify_bundle.py --bundle /existing/bundle --trusted-root /trusted/public-root.json --state /private/client-state --target node2shell-rules.json --output /review/new-rules.json
38+
.venv-tuf/bin/python -m unittest discover -s tools/signed_rules -v
39+
```
40+
41+
On Windows use `.venv-tuf\Scripts\python.exe`. The output parent and bundle must
42+
already exist. A bundle has flat `metadata/` and `targets/` directories containing
43+
standard TUF versioned metadata and hash-prefixed target files. Delegations are
44+
not supported. The public root is a separate, explicitly pinned input.
45+
Target names must be flat JSON filenames; target bytes must be UTF-8 without a
46+
BOM and decode to JSON objects with unique keys and finite numeric values. The
47+
public trust root must be located separately from the supplied bundle.
48+
No claims about a universal advisory schema are made: the subsequent reviewer
49+
must validate the target against its particular checker's schema and primary
50+
sources. A signed arbitrary JSON object is not an accepted detection rule.
51+
52+
All files read are limited to four MiB, new root rotations to 32 per invocation (in addition to authenticated cached
53+
history replay), and delegation
54+
traversal to zero. Bundle fetches use a local-only custom FetcherInterface; the
55+
normal python-tuf HTTP fetcher, proxy behavior and retries are never used.
56+
Symbolic links and path traversal in selected bundle paths are rejected. The
57+
cache permits only python-tuf's own relative `root.json` convenience link into
58+
its non-symlink `root_history/`; other cache links are rejected. Keep
59+
bundle, root, state and output under directories controlled by the invoking user;
60+
this is not a hostile concurrent filesystem sandbox.
61+
62+
## Failure, rollback and release procedure
63+
64+
Keep the state directory durable, private and dedicated to one original public
65+
root. A create-exclusive lock prevents cooperating concurrent updates. Do not
66+
delete the state directory or its metadata to bypass expiration or rollback
67+
rejection. The bootstrap hash binds it to the exact originally provisioned root;
68+
a differently serialized or replaced bootstrap is rejected. If a process is
69+
killed without cleanup, verify that no updater still owns `update.lock` before
70+
manually removing that lock only. Restore lost state from a trusted backup or
71+
re-provision under the publisher's recovery procedure; this tool cannot detect
72+
a hostile administrator deleting or rolling back its local state.
73+
74+
python-tuf persists verified metadata as verification advances, including when
75+
a later step fails. Keeping this newer metadata prevents replay of older releases.
76+
Failed signature/hash/length, expired metadata, missing targets, malformed JSON,
77+
unknown publishers or rollback never replace an existing output. The verifier
78+
exits 2 for errors (130 for interruption), writes only a generic failure message,
79+
and retains embedded checker rules. Existing output requires explicit `--force`
80+
and is replaced atomically only after verification. Rollback protection concerns
81+
signed metadata versions; a trusted publisher can still intentionally sign older
82+
content under a new version. Review changelog and advisory provenance before
83+
releasing any rule change. Never use force or cache deletion as a rollback policy.
84+
85+
Release procedure: verify a local bundle; review the authenticated artifact's
86+
schema, source links, uncertainty and thresholds; add boundary tests and dated
87+
methodology; regenerate the embedded snapshot; obtain Copilot review and passing
88+
CI for the exact commit; publish the normal checker release with a changelog.
89+
Emergency rollback is a new reviewed, signed release with increasing metadata
90+
versions. Failed updates leave the last released embedded rules available offline.
91+
92+
Tests cover real Ed25519 signatures, tampering, expiry, rollback, threshold-bound
93+
root rotation, untrusted root/release keys, protected outputs and lock ownership.
94+
They use synthetic data and prohibit socket creation. Production trust provisioning
95+
and an authenticated hosted feed are operational work, not claimed delivered by
96+
these fixtures.
97+
98+
Cached roots are replayed cryptographically from the original bootstrap on every
99+
invocation. Their count is added to the new-rotation budget so routine root
100+
rotations do not exhaust a lifetime limit. History is bounded to 4,096 regular
101+
root files and 64 MiB; reaching that operational cap requires a publisher-planned
102+
checkpoint/reprovisioning release, never bypassing verification or deleting cache.
103+
104+
Targets must carry a signed SHA-256 digest as well as their signed length; hashless targets are rejected. No-force publication requires a filesystem supporting hard links and fails closed otherwise. Only direct optional dependencies are version-pinned; transitive dependencies resolve through pip, so release environments should separately lock and audit the complete resolved environment.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Optional tooling only; the standalone checker and root tests do not import these.
2+
tuf==7.0.1
3+
securesystemslib[crypto]==1.6.0

0 commit comments

Comments
 (0)