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.
- watchlight_enclave-0.2.1/.gitignore +18 -0
- watchlight_enclave-0.2.1/CHANGELOG.md +303 -0
- watchlight_enclave-0.2.1/CODE_OF_CONDUCT.md +70 -0
- watchlight_enclave-0.2.1/CONTRIBUTING.md +142 -0
- watchlight_enclave-0.2.1/LICENSE +201 -0
- watchlight_enclave-0.2.1/PKG-INFO +132 -0
- watchlight_enclave-0.2.1/README-pypi.md +98 -0
- watchlight_enclave-0.2.1/README.md +337 -0
- watchlight_enclave-0.2.1/SECURITY.md +108 -0
- watchlight_enclave-0.2.1/crates/wl-enclave-egress/Cargo.lock +213 -0
- watchlight_enclave-0.2.1/crates/wl-enclave-egress/Cargo.toml +22 -0
- watchlight_enclave-0.2.1/crates/wl-enclave-egress/Dockerfile +39 -0
- watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/allowlist.rs +245 -0
- watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/main.rs +589 -0
- watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/netguard.rs +299 -0
- watchlight_enclave-0.2.1/crates/wl-enclave-egress/src/recorder.rs +588 -0
- watchlight_enclave-0.2.1/docs/README.md +365 -0
- watchlight_enclave-0.2.1/docs/design/README.md +101 -0
- watchlight_enclave-0.2.1/docs/design/attestation.md +128 -0
- watchlight_enclave-0.2.1/docs/design/drivers.md +138 -0
- watchlight_enclave-0.2.1/docs/design/egress.md +150 -0
- watchlight_enclave-0.2.1/docs/design/evidence.md +206 -0
- watchlight_enclave-0.2.1/docs/design/limits.md +77 -0
- watchlight_enclave-0.2.1/docs/design/trust-boundaries.md +142 -0
- watchlight_enclave-0.2.1/examples/coder/Dockerfile +22 -0
- watchlight_enclave-0.2.1/examples/coder/README.md +150 -0
- watchlight_enclave-0.2.1/examples/coder/agent.py +107 -0
- watchlight_enclave-0.2.1/examples/coder/enclave.yaml +25 -0
- watchlight_enclave-0.2.1/examples/coder/policies/coder.policy.json +10 -0
- watchlight_enclave-0.2.1/pyproject.toml +86 -0
- watchlight_enclave-0.2.1/src/wl_enclave/__init__.py +8 -0
- watchlight_enclave-0.2.1/src/wl_enclave/attestation.py +263 -0
- watchlight_enclave-0.2.1/src/wl_enclave/cli.py +1057 -0
- watchlight_enclave-0.2.1/src/wl_enclave/config.py +366 -0
- watchlight_enclave-0.2.1/src/wl_enclave/drivers/__init__.py +329 -0
- watchlight_enclave-0.2.1/src/wl_enclave/drivers/docker.py +630 -0
- watchlight_enclave-0.2.1/src/wl_enclave/egress/__init__.py +274 -0
- watchlight_enclave-0.2.1/src/wl_enclave/egress/local.py +549 -0
- watchlight_enclave-0.2.1/src/wl_enclave/evidence.py +850 -0
- watchlight_enclave-0.2.1/src/wl_enclave/runs.py +247 -0
- watchlight_enclave-0.2.1/src/wl_enclave/signing.py +332 -0
- watchlight_enclave-0.2.1/tests/test_config.py +336 -0
- watchlight_enclave-0.2.1/tests/test_egress_attestation.py +195 -0
- watchlight_enclave-0.2.1/tests/test_evidence.py +323 -0
- watchlight_enclave-0.2.1/tests/test_forgery_resistance.py +828 -0
- watchlight_enclave-0.2.1/tests/test_hosts.py +147 -0
- 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.
|