watchlight-enclave 0.2.1__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 (47) hide show
  1. watchlight_enclave-0.2.1/.gitignore +18 -0
  2. watchlight_enclave-0.2.1/CHANGELOG.md +303 -0
  3. watchlight_enclave-0.2.1/CODE_OF_CONDUCT.md +70 -0
  4. watchlight_enclave-0.2.1/CONTRIBUTING.md +142 -0
  5. watchlight_enclave-0.2.1/LICENSE +201 -0
  6. watchlight_enclave-0.2.1/PKG-INFO +132 -0
  7. watchlight_enclave-0.2.1/README-pypi.md +98 -0
  8. watchlight_enclave-0.2.1/README.md +337 -0
  9. watchlight_enclave-0.2.1/SECURITY.md +108 -0
  10. watchlight_enclave-0.2.1/crates/wl-enclave-egress/Cargo.lock +213 -0
  11. watchlight_enclave-0.2.1/crates/wl-enclave-egress/Cargo.toml +22 -0
  12. watchlight_enclave-0.2.1/crates/wl-enclave-egress/Dockerfile +39 -0
  13. watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/allowlist.rs +245 -0
  14. watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/main.rs +589 -0
  15. watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/netguard.rs +299 -0
  16. watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/recorder.rs +588 -0
  17. watchlight_enclave-0.2.1/docs/README.md +365 -0
  18. watchlight_enclave-0.2.1/docs/design/README.md +101 -0
  19. watchlight_enclave-0.2.1/docs/design/attestation.md +128 -0
  20. watchlight_enclave-0.2.1/docs/design/drivers.md +138 -0
  21. watchlight_enclave-0.2.1/docs/design/egress.md +150 -0
  22. watchlight_enclave-0.2.1/docs/design/evidence.md +206 -0
  23. watchlight_enclave-0.2.1/docs/design/limits.md +77 -0
  24. watchlight_enclave-0.2.1/docs/design/trust-boundaries.md +142 -0
  25. watchlight_enclave-0.2.1/examples/coder/Dockerfile +22 -0
  26. watchlight_enclave-0.2.1/examples/coder/README.md +150 -0
  27. watchlight_enclave-0.2.1/examples/coder/agent.py +107 -0
  28. watchlight_enclave-0.2.1/examples/coder/enclave.yaml +25 -0
  29. watchlight_enclave-0.2.1/examples/coder/policies/coder.policy.json +10 -0
  30. watchlight_enclave-0.2.1/pyproject.toml +86 -0
  31. watchlight_enclave-0.2.1/src/wl_enclave/__init__.py +8 -0
  32. watchlight_enclave-0.2.1/src/wl_enclave/attestation.py +263 -0
  33. watchlight_enclave-0.2.1/src/wl_enclave/cli.py +1057 -0
  34. watchlight_enclave-0.2.1/src/wl_enclave/config.py +366 -0
  35. watchlight_enclave-0.2.1/src/wl_enclave/drivers/__init__.py +329 -0
  36. watchlight_enclave-0.2.1/src/wl_enclave/drivers/docker.py +630 -0
  37. watchlight_enclave-0.2.1/src/wl_enclave/egress/__init__.py +274 -0
  38. watchlight_enclave-0.2.1/src/wl_enclave/egress/local.py +549 -0
  39. watchlight_enclave-0.2.1/src/wl_enclave/evidence.py +850 -0
  40. watchlight_enclave-0.2.1/src/wl_enclave/runs.py +247 -0
  41. watchlight_enclave-0.2.1/src/wl_enclave/signing.py +332 -0
  42. watchlight_enclave-0.2.1/tests/test_config.py +336 -0
  43. watchlight_enclave-0.2.1/tests/test_egress_attestation.py +195 -0
  44. watchlight_enclave-0.2.1/tests/test_evidence.py +323 -0
  45. watchlight_enclave-0.2.1/tests/test_forgery_resistance.py +828 -0
  46. watchlight_enclave-0.2.1/tests/test_hosts.py +147 -0
  47. watchlight_enclave-0.2.1/tests/test_signing.py +396 -0
@@ -0,0 +1,18 @@
1
+ # build + runtime
2
+ target/
3
+ __pycache__/
4
+ *.py[cod]
5
+ .venv/
6
+ dist/
7
+ build/
8
+
9
+ # enclave run artifacts
10
+ .watchlight/
11
+ runs/
12
+ *.tgz
13
+
14
+ # os
15
+ .DS_Store
16
+
17
+ # shoalscan scan output (the baseline and policy ARE committed)
18
+ .shoalscan/runs/
@@ -0,0 +1,303 @@
1
+ # Changelog
2
+
3
+ Notable changes to Watchlight AI Enclave. Format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## Three contracts, versioned separately
7
+
8
+ This project has three promises, and only the first is what the version number
9
+ on the tag tracks. Knowing which one a change touches is the difference between
10
+ a routine upgrade and a broken pipeline.
11
+
12
+ **1. The CLI surface and `enclave.yaml` schema.** What the tag versions.
13
+ Pre-1.0, these may change in a minor release.
14
+
15
+ **2. How strictly `wl-enclave verify` reads a bundle.** Not covered by the
16
+ format version, and the one most likely to surprise you: *a stricter verifier
17
+ is a breaking change for a CI gate even when the bundle format is identical.* A
18
+ bundle that exited 0 on one version can exit 1 on the next because the verifier
19
+ learned to notice something it used to miss. Every release that tightens
20
+ `verify` says so under **Verifier** below, in those words.
21
+
22
+ **3. The bundle format**, currently `wl-enclave-evidence/1`. This moves only
23
+ when an older verifier would *misread* a newer bundle. Adding a field is not a
24
+ bump, because old verifiers ignore unknown fields. Changing what an existing
25
+ field means is a bump. This number has to age well: a bundle in someone's
26
+ compliance archive must stay verifiable for as long as they keep it.
27
+
28
+ 1.0 is gated on the manifest reaching its final shape. Operator signing is in
29
+ (see 0.2.0), but witness cosignatures ([#2–#6]) are the part that decides
30
+ what a sealed manifest ultimately holds. Promising format stability before then
31
+ would mean breaking that promise immediately.
32
+
33
+ ## [Unreleased]
34
+
35
+ Nothing yet.
36
+
37
+ ## [0.2.1] — 2026-11-10
38
+
39
+ **Packaging, documentation, and what `keygen` prints.** No change to
40
+ `enclave.yaml`, to how strictly `verify` reads a bundle, or to the bundle
41
+ format. Nothing here can change the verdict on a bundle you already hold. The
42
+ one behavioural change is two output labels in `keygen`, which matters only if
43
+ you parse them.
44
+
45
+ This is also the release where the repository became public.
46
+
47
+ This is the first release on PyPI: `pip install watchlight-enclave`. 0.2.0 was
48
+ tagged before the publishing workflow existed, and a publish must come from a
49
+ tag that carries it, so the first published version is this one. 0.2.0 remains
50
+ a GitHub release and an install from source; nothing was published under it.
51
+
52
+ ### Added
53
+
54
+ - **`publish-pypi.yml`** — publishing over OIDC trusted publishing, so no
55
+ long-lived API token exists in the repository or anywhere else. Manual, and
56
+ a dry run unless told otherwise. Four gates, each refusing rather than
57
+ warning, because a PyPI filename is permanent and a bad upload cannot be
58
+ corrected in place, only yanked:
59
+ - a publish must come from a `v*` tag;
60
+ - `pyproject.toml`, `wl_enclave.__version__` and the tag must agree, and
61
+ exactly one wheel and one sdist must name that version;
62
+ - no key or credential material in either distribution — `wl-enclave
63
+ keygen` writes Ed25519 private keys as `.pem` files, so a key left in the
64
+ working tree is a real way to publish one by accident — and no wheel
65
+ missing `evidence.py` or `signing.py`, since the verifier is the thing
66
+ third parties are promised;
67
+ - the built wheel is installed in a clean virtualenv and run, including
68
+ `keygen`, which exercises the lazy `cryptography` import through the real
69
+ entry point. An undeclared dependency would otherwise surface only when a
70
+ user tried to sign.
71
+ - **`github-release.yml`** — release notes taken from this file rather than
72
+ written twice. The notes state which of the three contracts a release moved,
73
+ and a reader deciding whether an upgrade breaks their CI gate relies on that
74
+ being accurate; a second copy drifts. A missing or empty section for the
75
+ tagged version fails the run rather than falling back to generated notes.
76
+
77
+ ### Documentation
78
+
79
+ - **[`SECURITY.md`](SECURITY.md)** — private disclosure, mirroring the
80
+ [Developer Edition](https://github.com/watchlight-ai-beacon/watchlight-de)
81
+ policy: same routes, same response windows, same safe harbour. It adds one
82
+ section that policy does not need — *what we treat as most severe*. A bundle
83
+ that understates what happened while still verifying ranks above remote code
84
+ execution in the CLI here, so the policy says so outright, lists the four
85
+ shapes it takes, and names the two limits that are already documented
86
+ (key-holder re-seal, and traffic the topology blocks but the guard never
87
+ sees) so nobody spends a weekend rediscovering them.
88
+ - **[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md)** — the Developer Edition file
89
+ verbatim, Contributor Covenant 2.1, reports to conduct@watchlight.ai.
90
+ - **A package page of its own.** `readme` points at `README-pypi.md`, 4.2 kB
91
+ rather than 18 kB. The repository README is for someone deciding whether to
92
+ trust the design; a package page's job is to get you installed and then send
93
+ you to the repository, which is where the documentation lives.
94
+ - **`[project.urls]`** was absent, so the package page had no route back to the
95
+ source. For a project whose claim is that you can read the verifier
96
+ yourself, that is the one link that has to work.
97
+ - `CONTRIBUTING.md` told you to open a public issue for a weakness in the
98
+ evidence model and said a security policy was coming. Both are now wrong. It
99
+ also documents that `main` requires an approving review, and that the
100
+ maintainer bypass does not extend to the status checks.
101
+ - The README dropped a duplicated paragraph and a status line claiming no
102
+ releases existed, and gained a section pointing at Developer Edition for
103
+ anyone who arrived via a bundle and wants to know where the decisions in it
104
+ come from.
105
+
106
+ ### Changed
107
+
108
+ - **`keygen` says `private key` and `public key id`**, where it said `key` and
109
+ `key id`. One is a file to protect and the other is a string to publish, and
110
+ the old labels left the reader to work that out. Getting it backwards goes
111
+ wrong in both directions: pasting a private key into a ticket, or withholding
112
+ the one value a reviewer needs in order to pin a signer. The command now also
113
+ states that the private key stays in its file and is not copied into a
114
+ bundle, mounted into the sandbox, or sent anywhere — the question the old
115
+ output invited and did not answer. **If you parse `keygen` output, match on
116
+ `public key id`.**
117
+ - `signing.exposed_by()` is documented correctly. It is the check that refuses
118
+ a run when the signing key sits inside a directory mounted into the sandbox,
119
+ which is the guard against an agent reading the key that signs its own
120
+ evidence. Its docstring described the symlink case backwards, and the test
121
+ covering that case was named for the opposite of what it asserted. The
122
+ behaviour was right; only the description was wrong.
123
+ - The sdist is an explicit include list instead of hatchling's default, which
124
+ is the whole working tree minus VCS ignores. The files it had been sweeping
125
+ in were harmless — CI config, a scanner baseline — but the default is *open*,
126
+ so anything added to the repository later would ship by default and nobody
127
+ would find out until it was on an immutable index. `tests/` and `crates/`
128
+ are included deliberately: the forgery tests are how a third party confirms
129
+ the verifier refuses what it says it refuses, and the guard crate is where
130
+ record-before-forward lives.
131
+
132
+ ## [0.2.0] — 2026-11-10
133
+
134
+ ### Added
135
+
136
+ - **`wl-enclave hosts [--since DATE]`** — every host the agent reached, across
137
+ runs, with verdict, attempt count, bytes each way and the address actually
138
+ dialled. The records existed from the first release and there was no way to
139
+ read them back, so the product recorded the thing it is best at and could not
140
+ show you. A run whose bundle does not verify is **excluded and announced**,
141
+ because a shorter, tidier host list is the one way this command could
142
+ mislead; the exit status is non-zero whenever the table is not both
143
+ trustworthy and complete, and the guard's coverage
144
+ (`http_connect` only) prints beside the answer rather than in a footnote.
145
+
146
+ - **Signed manifests.** `wl-enclave keygen` writes an Ed25519 key (default
147
+ `~/.config/wl-enclave/signing.key`, mode 0600, outside both the repository
148
+ and the bundle); `run` seals a signature over the manifest body when a key
149
+ resolves. A key named explicitly — `--sign-key` or `$WL_ENCLAVE_SIGNING_KEY`
150
+ — that cannot be read is an error, because sealing unsigned after being asked
151
+ to sign is the kind of quiet downgrade this format exists to prevent. No key
152
+ anywhere still seals unsigned, so an install that never runs `keygen` behaves
153
+ as it did. [#2–#6]
154
+ - **`verify --signer KEY_ID`** and **`verify --require-signature`**. The first
155
+ is the only form of the check that establishes *who* sealed a bundle. The
156
+ second accepts any key but requires one.
157
+ - **`cosignatures` in every manifest**, written empty, and verified when
158
+ present. A witness the signer does not run is what would make a bundle
159
+ evidence against its operator rather than only against the agent; reserving
160
+ and checking the slot now means adding one later changes neither the bundle
161
+ format nor the verifier.
162
+
163
+ ### Changed
164
+
165
+ - **`verify` grades signing in three steps rather than two.** The summary reads
166
+ `unsigned`, `self-signed`, or `signed by the expected key`. `self-signed` is
167
+ not a hedge: the public key travels inside the bundle, so a verifying
168
+ signature establishes that the manifest is unchanged and *not* who sealed it.
169
+ Anyone can produce a bundle in that state.
170
+ - **One manifest payload.** Four seal sites built this independently and three
171
+ disagreed, which was survivable while the manifest was only read and is not
172
+ once it is signed.
173
+ - New runtime dependency: `cryptography>=42.0`. Imported lazily, so verifying
174
+ an *unsigned* bundle still works if it is missing.
175
+
176
+ ### Verifier
177
+
178
+ **This release does not tighten `verify` by default.** `--signer` and
179
+ `--require-signature` are opt-in, so a gate standing on a bundle sealed by
180
+ 0.1.0 keeps passing. Two new ways to fail exist once you opt in: an unsigned
181
+ bundle under `--require-signature` reads `incomplete` (everything in it is
182
+ still true; what is missing is attribution), while a signature that does not
183
+ verify, names a `key_id` its own public key does not produce, carries an
184
+ unrecognised algorithm, or belongs to a key other than the pinned one reads
185
+ `failed` (a contradiction, not a gap).
186
+
187
+ ### Format
188
+
189
+ `wl-enclave-evidence/1` is unchanged. `signature` and `cosignatures` are added
190
+ fields, and a verifier that predates them reports a signed bundle as
191
+ `unsigned` — weaker than the truth, which is the only direction a stale
192
+ verifier may err.
193
+
194
+ ### Known gaps
195
+
196
+ - **A signature is not a completeness proof.** The key holder can truncate the
197
+ chain, write a `run.close` whose counts agree with what is left, re-seal, and
198
+ the bundle verifies as `ok` against a pinned key with a denial missing.
199
+ `tests/test_signing.py::test_a_key_holder_can_still_reseal_a_truncated_chain`
200
+ performs that forgery and asserts it *succeeds*, so the limit cannot become a
201
+ claim by accident. Only a witness cosignature closes it. [#2–#6]
202
+
203
+ ## [0.1.0] — 2026-11-05
204
+
205
+ First tagged version. Previously unreleased, so there is no upgrade path to
206
+ describe — but the entries below are the state of the three contracts as of
207
+ this tag.
208
+
209
+ ### Evidence
210
+
211
+ - **Four verdicts instead of a boolean.** `OK`, `INCOMPLETE`, `UNCONTAINED`,
212
+ `FAILED`, as the worst of three independent claims (integrity, containment,
213
+ completeness). Only `OK` exits 0, so a CI gate can branch on the exit code
214
+ alone. `UNCONTAINED` is deliberately not a milder `FAILED`: `FAILED` means
215
+ *do not trust this file*, `UNCONTAINED` means *trust it and act on what it
216
+ says*.
217
+ - **Named controls with provenance.** Each control records whether it was in
218
+ force *and how that was established* — `runtime_readback`,
219
+ `network_topology`, `runtime_config_readback`, or
220
+ `requested_not_readback`. The last one establishes nothing, and that is
221
+ enforced rather than documented: a control resting on it is reported as
222
+ unverified whatever it claims.
223
+ - **Containment and witnessing are separate claims.** A guard that dies on an
224
+ internal network leaves the sandbox *more* contained and *less* witnessed, so
225
+ that reads `INCOMPLETE`, not `UNCONTAINED`.
226
+ - **Governing digests.** `config.attested` records the digest of `enclave.yaml`
227
+ and every policy file that governed the run.
228
+ - **The guard build is identified.** `egress.guard_started` carries the image
229
+ digest the daemon ran (not self-reported, so the guard cannot lie about it)
230
+ and the version the binary announced on startup (self-reported, but the only
231
+ one that survives an image rebuilt under the same tag).
232
+
233
+ ### Verifier
234
+
235
+ Stated here because this is the section future releases will use to warn you.
236
+
237
+ - **`verify` requires records that must exist to exist.** A run must have
238
+ exactly one terminal record; a guard that started owes an attestation; a
239
+ `run.close` tally is cross-checked against the chain. Without these,
240
+ absence was indistinguishable from compliance and the emptiest bundle looked
241
+ like the cleanest one.
242
+ - **`run.error` caps at `INCOMPLETE`.** A run that died during startup attested
243
+ nothing, and the absence of those records is not evidence of a clean run.
244
+ - **Unrecognised control names are reported** rather than sorted into a claim
245
+ by guesswork.
246
+
247
+ ### Containment
248
+
249
+ - **The sandbox never runs as root.** The driver passes `--user` regardless of
250
+ what the image declares, because an image with no `USER` directive runs as
251
+ uid 0 and most published base images have none. Attested as
252
+ `runs_as_non_root`, read with `id -u` from inside rather than from the
253
+ runtime's config.
254
+ - **The evidence mask is confirmed by effect, not by configuration.** A canary
255
+ planted behind the mask must be unreadable from inside *and* a canary at an
256
+ unmasked path must be readable in the same probe round — otherwise "the mask
257
+ held" and "we could not observe anything" are the same observation.
258
+ - Read-only rootfs, all capabilities dropped, `no-new-privileges`, and an
259
+ internal network with the egress guard as the only route.
260
+
261
+ ### Egress
262
+
263
+ - **Recording is a precondition for forwarding.** If the guard cannot record an
264
+ attempt, it refuses the connection. A refused connection is a denial of
265
+ service the agent can see and the bundle explains; an unrecorded one is a
266
+ silent hole.
267
+ - **Saturation notes have their own budget**, so a flood of cheap payload-free
268
+ connections cannot exhaust the budget that real records need and leave a
269
+ later tunnel unrecorded.
270
+ - **Malformed requests are recorded** and claim no host, because none was
271
+ parsed. Guessing one would put an attacker-chosen string in the field a
272
+ reviewer reads as a destination.
273
+ - Addresses are resolved once, validated, and the validated address is dialled
274
+ — never the hostname again, which is what DNS rebinding relies on. The
275
+ address actually dialled is recorded.
276
+
277
+ ### Known gaps
278
+
279
+ Each is stated in the bundle or the docs rather than left to be discovered.
280
+
281
+ - **Bundles are unsigned.** The chain catches edits, not truncation, and says
282
+ nothing about origin. `verify` prints this on every run. [#2–#6]
283
+ *(Addressed in part under Unreleased: manifests can now be signed, which
284
+ moves the forger from anyone with write access to the key holder. It does not
285
+ close truncation by the key holder.)*
286
+ - **Traffic blocked by topology is not recorded.** A raw socket to an arbitrary
287
+ port has no route, so the guard never sees it: blocked, but not described.
288
+ The bundle states this in `blocked_not_recorded`. [#12]
289
+ - **Decision records are cooperative.** The SDK is a library the agent calls.
290
+ An agent that does not call it produces no decision records, and the bundle
291
+ cannot distinguish that from an agent that made none. The egress records do
292
+ not have this problem, because the agent cannot route around the network
293
+ topology — so the two streams are not equally strong.
294
+ - **The workspace is writable**, so a run can edit the rules the next run is
295
+ held to. Made visible with digests rather than prevented. [#13]
296
+
297
+ [Unreleased]: https://github.com/watchlight-ai-beacon/watchlight-enclave/compare/v0.2.1...HEAD
298
+ [0.2.1]: https://github.com/watchlight-ai-beacon/watchlight-enclave/releases/tag/v0.2.1
299
+ [0.2.0]: https://github.com/watchlight-ai-beacon/watchlight-enclave/releases/tag/v0.2.0
300
+ [0.1.0]: https://github.com/watchlight-ai-beacon/watchlight-enclave/releases/tag/v0.1.0
301
+ [#2–#6]: https://github.com/watchlight-ai-beacon/watchlight-enclave/issues/2
302
+ [#12]: https://github.com/watchlight-ai-beacon/watchlight-enclave/issues/12
303
+ [#13]: https://github.com/watchlight-ai-beacon/watchlight-enclave/issues/13
@@ -0,0 +1,70 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ - Demonstrating empathy and kindness toward other people
21
+ - Being respectful of differing opinions, viewpoints, and experiences
22
+ - Giving and gracefully accepting constructive feedback
23
+ - Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ - Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ - The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ - Trolling, insulting or derogatory comments, and personal or political attacks
33
+ - Public or private harassment
34
+ - Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ - Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+
56
+ ## Enforcement
57
+
58
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
59
+ reported to the community leaders responsible for enforcement at
60
+ **conduct@watchlight.ai**. All complaints will be reviewed and investigated
61
+ promptly and fairly. All community leaders are obligated to respect the privacy
62
+ and security of the reporter of any incident.
63
+
64
+ ## Attribution
65
+
66
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
67
+ version 2.1, available at
68
+ <https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.
69
+
70
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,142 @@
1
+ # Contributing
2
+
3
+ A driver for a new sandbox, a guard for a new enforcement point, or a hole in
4
+ the evidence model — all welcome, and none of them need our permission.
5
+
6
+ ## The one thing to internalise
7
+
8
+ Everything here serves a single claim:
9
+
10
+ > **A bundle must never be able to understate what happened.**
11
+
12
+ A bundle that is missing records *while looking complete* is worse than one
13
+ that admits a gap, because a clean bundle gets read as proof of innocence. So
14
+ the codebase is tilted one way throughout: a layer that cannot record stops
15
+ working rather than working quietly, a check that cannot be made says so, and
16
+ where two readings are possible the pessimistic one is recorded.
17
+
18
+ If a change makes a bundle quieter about its own limits, it is going the wrong
19
+ way even if every test passes. [`docs/design/`](docs/design/README.md) explains
20
+ the rest.
21
+
22
+ ## Getting set up
23
+
24
+ ```bash
25
+ git clone https://github.com/watchlight-ai-beacon/watchlight-enclave
26
+ cd watchlight-enclave
27
+ python -m venv .venv && . .venv/bin/activate
28
+ pip install -e ".[dev]"
29
+
30
+ pytest -q
31
+ cargo test --manifest-path crates/wl-enclave-egress/Cargo.toml --locked
32
+ ```
33
+
34
+ You need a container runtime for the full suite. Docker, Colima and Podman all
35
+ work.
36
+
37
+ **Run the real thing before you believe the tests:**
38
+
39
+ ```bash
40
+ wl-enclave -C examples/coder run --purpose "checking my change"
41
+ wl-enclave verify examples/coder/runs/<id>/bundle
42
+ ```
43
+
44
+ ### A warning about your laptop
45
+
46
+ Docker Desktop and Colima map file ownership permissively through virtiofs.
47
+ Native Linux does not. A container running as a fixed unprivileged uid cannot
48
+ write a bind-mounted file owned by the host user — which is why the guard once
49
+ died on startup on Linux while passing on macOS every time.
50
+
51
+ CI runs on Linux. If a change touches mounts, uids or anything the container
52
+ writes back to the host, **let CI see it before you trust it.** That is not CI
53
+ being pedantic; it is the only Linux most of us test on.
54
+
55
+ ## Before you open a PR
56
+
57
+ ```bash
58
+ pytest -q
59
+ cargo fmt --manifest-path crates/wl-enclave-egress/Cargo.toml
60
+ cargo clippy --manifest-path crates/wl-enclave-egress/Cargo.toml --locked --all-targets -- -D warnings
61
+ ```
62
+
63
+ Then make sure your test fails without your fix. Revert the fix, watch it fail,
64
+ put it back. This is the step most worth doing and the easiest to skip — we
65
+ have shipped a test that passed because its probe was aimed at a path that did
66
+ not exist, which is indistinguishable from a test passing because the control
67
+ holds.
68
+
69
+ ## Commits
70
+
71
+ Conventional subjects — `fix:`, `feat:`, `docs:`, `ci:`, `chore:`, `!` for
72
+ breaking — and every commit signed off under the
73
+ [DCO](https://developercertificate.org/):
74
+
75
+ ```bash
76
+ git commit -s
77
+ ```
78
+
79
+ Commit bodies are for *why*, including what you tried that did not work. The
80
+ diff covers what.
81
+
82
+ ## Review
83
+
84
+ CI must be green — with one documented exception. The **end to end** job is
85
+ flaky and is not a required check, because it is reporting a real race ([#22]):
86
+ the sandbox is attested after the agent has started, so whether the evidence
87
+ mask can be confirmed from inside depends on whether the agent is still alive
88
+ when we probe. It passes when attestation wins and fails when it loses.
89
+
90
+ It is excluded rather than masked with `continue-on-error`, because hiding a
91
+ job that reports a real gap is the same mistake as a bundle that looks complete
92
+ while omitting records. **If you see it pass, that is not evidence the race is
93
+ gone** — and if you make it pass reliably, make sure you fixed the ordering
94
+ rather than the assertion.
95
+
96
+ [#22]: https://github.com/watchlight-ai-beacon/watchlight-enclave/issues/22
97
+
98
+ Every pull request needs an approving review before it merges, and `main` takes
99
+ no direct pushes. Approvals are dismissed when new commits land, so a review is
100
+ of the code that actually merges rather than of an earlier draft. Maintainers
101
+ can bypass this to land their own work; that is a deliberate exception for a
102
+ small team, not the normal path, and it does not extend to the status checks.
103
+
104
+ Beyond that, the questions a reviewer will actually ask:
105
+
106
+ **Does a bundle still state its own limits?** A new field that could be absent,
107
+ a new control that could be unverified, a new way a record could be dropped —
108
+ does `verify` notice, or does it stay quiet?
109
+
110
+ **Is the `verified_by` level honest?** `requested_not_readback` establishes
111
+ nothing, and that is enforced. If you reach for it, the control is not
112
+ established.
113
+
114
+ **Could a second implementation satisfy this seam while producing weaker
115
+ evidence, with nothing failing?** The question that matters most for drivers
116
+ and guards, because weaker evidence looks identical to honest evidence in a
117
+ bundle.
118
+
119
+ **What breaks for someone with a CI gate?** If `verify` got stricter, say so in
120
+ the CHANGELOG, in those words.
121
+
122
+ ## Reporting a weakness in the evidence model
123
+
124
+ If you can make a bundle understate what happened, that is the most valuable
125
+ bug report this project can receive — and it goes through
126
+ [`SECURITY.md`](SECURITY.md), privately, not into a public issue.
127
+ [Report a vulnerability](https://github.com/watchlight-ai-beacon/watchlight-enclave/security/advisories/new)
128
+ on the Security tab, or mail security@watchlight.ai.
129
+
130
+ That policy also lists what we already know and are not looking for reports
131
+ about — chiefly that a holder of the signing key can re-seal a truncated chain
132
+ — so you can tell quickly whether you have found something new.
133
+
134
+ Attestation gaps are fair game too: a control that claims more than it
135
+ established, a readback that reads back the thing we asked for, a probe that
136
+ cannot tell "the control held" from "we could not observe anything".
137
+
138
+ ## Code of conduct
139
+
140
+ By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
141
+ Concerns go to conduct@watchlight.ai, which is read by people who can act on
142
+ them.