scpe-protocol 0.2.0__tar.gz → 0.2.2__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.
- {scpe_protocol-0.2.0/scpe_protocol.egg-info → scpe_protocol-0.2.2}/PKG-INFO +64 -21
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/README.md +313 -270
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/pyproject.toml +56 -56
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/producer.py +13 -4
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/standalone/verify_envelope.py +42 -4
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/__init__.py +16 -16
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/cli.py +22 -2
- scpe_protocol-0.2.2/scpe/context.py +123 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/results.py +42 -5
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/seal.py +41 -12
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2/scpe_protocol.egg-info}/PKG-INFO +64 -21
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/SOURCES.txt +3 -0
- scpe_protocol-0.2.2/tests/test_action_expressions.py +69 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_action_yaml.py +561 -508
- scpe_protocol-0.2.2/tests/test_context_binding.py +195 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_results_contract.py +1 -1
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_seal.py +50 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/LICENSE +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/LICENSE-SPEC +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/__init__.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/disclosure.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/level1_lint.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/reference/standalone/__init__.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/diffinfo.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/optin.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/testrun.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe/verify.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/dependency_links.txt +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/entry_points.txt +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/requires.txt +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/scpe_protocol.egg-info/top_level.txt +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/setup.cfg +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_diff_resolution.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_differential_verifiers.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_disclosure.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_e2e_local.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_empty_keys_anchor_parity.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_entry_points.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_gate_requires_forge_anchor.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_key_source_anchor.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_level1_lint.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_level2_implies_level1.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_no_duplicate_verifier.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_optin.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_package_is_stdlib_only.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_producer_cli.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_producer_roundtrip.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_producer_submit.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_profiles_demo.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_retired_envelope_format.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_seal_render.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_seal_results.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_spec_vectors.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_ssrf_fetch.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_stress_100prs.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_testrun.py +0 -0
- {scpe_protocol-0.2.0 → scpe_protocol-0.2.2}/tests/test_verify_cli_parity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scpe-protocol
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: SCPE: an open protocol for verifiable provenance of any digital artifact — who signed a contribution or file, what they declared about AI use, and that nothing was tampered with, verified offline with no server and no new accounts. Ships the single-file reference verifier, its producer, and a CLI (`scpe`) that seals a pull request from it.
|
|
5
5
|
Author: Augusto Bastos
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -44,7 +44,11 @@ Dynamic: license-file
|
|
|
44
44
|
<img alt="spec" src="https://img.shields.io/badge/spec-scpe%2F0.1-41c257?style=flat-square&labelColor=0b0b0c">
|
|
45
45
|
<img alt="python" src="https://img.shields.io/badge/python-3.11%2B-41c257?style=flat-square&labelColor=0b0b0c">
|
|
46
46
|
<img alt="license" src="https://img.shields.io/badge/code-Apache--2.0-41c257?style=flat-square&labelColor=0b0b0c">
|
|
47
|
-
<img alt="status" src="https://img.shields.io/badge/v0.2-early-d29922?style=flat-square&labelColor=0b0b0c">
|
|
47
|
+
<img alt="status" src="https://img.shields.io/badge/v0.2.2-early-d29922?style=flat-square&labelColor=0b0b0c">
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
<a href="https://augbastos.github.io/scpe/"><b>augbastos.github.io/scpe</b></a> — what it is, in one page
|
|
48
52
|
</p>
|
|
49
53
|
|
|
50
54
|
An open protocol for two questions a pull request cannot answer today: **who signed this
|
|
@@ -146,6 +150,16 @@ SLSA uses to sell levels. See [docs/LEVELS.md](docs/LEVELS.md).
|
|
|
146
150
|
(target repo, base commit, a SHA-256 of the exact diff, an AI-use disclosure, and an
|
|
147
151
|
optional attribution record) signed with the SSH key already on their GitHub profile
|
|
148
152
|
(`ssh-keygen -Y sign -n scpe/0.1`). No new account.
|
|
153
|
+
|
|
154
|
+
> **The key has to be published as an authentication key.** `forge` verification reads
|
|
155
|
+
> `github.com/<login>.keys`, and GitHub serves only authentication keys there — a key added
|
|
156
|
+
> under *Signing keys* is real, is used by `git`, and **never appears at that URL**, so a
|
|
157
|
+
> contribution signed with it can only ever reach `key_source: bundled`. Add it with
|
|
158
|
+
> `gh ssh-key add <key>.pub --title scpe` (no `--type signing`). GitHub does publish signing
|
|
159
|
+
> keys, at `api.github.com/users/<login>/ssh_signing_keys`, but `<host>/<login>.keys` is the
|
|
160
|
+
> shape the fixed provider table is built on; reading the signing-key endpoint per provider is
|
|
161
|
+
> open, not decided. Filing the key in the obviously-correct place and silently losing forge
|
|
162
|
+
> verification is the first thing that went wrong for the protocol's own author.
|
|
149
163
|
2. It travels inside a **normal pull request** — the diff in the branch, the ~1–2 KB signed
|
|
150
164
|
attestation embedded in the PR body. Merging leaves the repo history clean.
|
|
151
165
|
3. The **owner's side** re-derives everything itself, with no SCPE server involved: the diff's
|
|
@@ -188,22 +202,44 @@ anchors and what a verdict is worth under each.
|
|
|
188
202
|
Add a workflow that verifies every PR and posts a seal. Set `require` to gate merges.
|
|
189
203
|
|
|
190
204
|
```yaml
|
|
191
|
-
# .github/workflows/scpe.yml —
|
|
192
|
-
-
|
|
205
|
+
# .github/workflows/scpe.yml — the step. Copy BOTH files from docs/workflows/ for the
|
|
206
|
+
# fork-safe version: scpe.yml (verify) and its companion scpe-seal.yml (post the seal).
|
|
207
|
+
- uses: augbastos/scpe@v0.2.2
|
|
193
208
|
with:
|
|
194
209
|
level: "1" # 1 = disclosure lint · 2 = signed envelope required
|
|
195
210
|
require: "true" # fail the check on anything not verifiable
|
|
196
211
|
```
|
|
197
212
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
213
|
+
### Version policy
|
|
214
|
+
|
|
215
|
+
Pin the exact tag while the protocol is pre-1.0.
|
|
216
|
+
|
|
217
|
+
- **Every tag is an immutable alias.** It points at one commit and is never moved. A fix
|
|
218
|
+
arrives as a *new* tag you adopt by editing the pin, never as a silent change under the one
|
|
219
|
+
you already wrote. Both `v0.2.1` and `v0.2.2` exist for that reason rather than because
|
|
220
|
+
`v0.2` was re-pointed.
|
|
221
|
+
- **The cost is that fixes do not reach you on their own** — including security fixes. Watch
|
|
222
|
+
the releases, or let Dependabot's `github-actions` ecosystem open the bump as a pull
|
|
223
|
+
request you can read before merging.
|
|
224
|
+
- **A patch tag can change behaviour when the old behaviour was wrong.** `v0.2.2` refuses
|
|
225
|
+
contributions that `v0.2.1` accepted, because `v0.2.1` accepted some it should not have.
|
|
226
|
+
Read the [CHANGELOG](CHANGELOG.md) before moving a pin; "patch" here means the protocol
|
|
227
|
+
did not change, not that nothing did.
|
|
228
|
+
- **`spec_version` is a separate number from the tag.** The protocol is `scpe/0.1` across
|
|
229
|
+
every `v0.2.x` release. See [CHANGELOG.md](CHANGELOG.md) for the three axes and why
|
|
230
|
+
reading one as the other is the easiest mistake to make here.
|
|
231
|
+
- **The `v0.1.x` line is legacy, not merely old.** It verifies an envelope format that is not
|
|
232
|
+
in `spec/`, so upgrading from it is a behaviour change and not a patch —
|
|
233
|
+
[docs/MIGRATION.md](docs/MIGRATION.md) has the steps.
|
|
234
|
+
|
|
235
|
+
**Which tag to be on:** `v0.2.2`. `v0.2.1` and `v0.2` accept a valid envelope replayed from
|
|
236
|
+
another repository (see [SECURITY.md](SECURITY.md)); `v0.2.2` refuses it.
|
|
237
|
+
|
|
238
|
+
The Action uses a fork-safe two-job split, and the two jobs live in **two files**: the untrusted
|
|
239
|
+
job in `scpe.yml` (which runs contributor code) holds no secrets, and only the trusted follow-up
|
|
240
|
+
job in `scpe-seal.yml` posts the comment. Two files is a GitHub constraint, not a preference — a
|
|
241
|
+
workflow that names itself in its own `workflow_run` trigger fails to register at all. Neither
|
|
242
|
+
level installs anything in the
|
|
207
243
|
runner — both run stdlib-only Python straight out of the Action's own checkout, so the bytes that
|
|
208
244
|
decide a merge are the bytes of the tag you pinned, not whatever a package index serves that day.
|
|
209
245
|
Check out with `fetch-depth: 0`: level 2 recomputes the diff as `git diff <base>...<head>`, and the
|
|
@@ -237,16 +273,20 @@ passing all eighteen does not by itself show that the `key_source` MUST is honou
|
|
|
237
273
|
checked by inspection. Every vector ships its own `keys` file so the suite runs offline, and no
|
|
238
274
|
vector reaches the `forge` anchor.
|
|
239
275
|
|
|
240
|
-
**Cost**
|
|
276
|
+
**Cost**
|
|
241
277
|
|
|
242
278
|
| | Measured |
|
|
243
279
|
|---|---|
|
|
244
280
|
| PR-body attestation (manifest + sig, base64) | 1.1–1.5 KB |
|
|
245
281
|
| Standalone envelope (3-file / 27-line PR, zipped) | ~1.5 KB |
|
|
246
|
-
| Verify wall-time | ~
|
|
282
|
+
| Verify wall-time | ~200 ms cold process · ~31 ms warm |
|
|
247
283
|
|
|
248
284
|
An `artifact` subject adds its payload size on top of ~800 B fixed overhead. Order-of-magnitude,
|
|
249
|
-
single machine — not a formal benchmark suite.
|
|
285
|
+
single machine — not a formal benchmark suite. Re-run it yourself rather than taking the number:
|
|
286
|
+
`python tests/bench_verify.py <envelope.zip> --keys <login.keys>` prints the median of 15 runs
|
|
287
|
+
for both paths. The cold figure is a fresh interpreter per run; the warm one re-verifies
|
|
288
|
+
in-process, which is what a batch consumer sees after import cost is paid once. Both use the
|
|
289
|
+
`flag` anchor, so no network is in the measurement.
|
|
250
290
|
|
|
251
291
|
## Where it sits
|
|
252
292
|
|
|
@@ -258,13 +298,16 @@ single machine — not a formal benchmark suite.
|
|
|
258
298
|
- **A different layer from build provenance.** Sigstore / SLSA / in-toto attest *artifacts and
|
|
259
299
|
builds*; SCPE attests a *contribution*, at the pull-request boundary.
|
|
260
300
|
- **Direct prior art:** `patatt` + `b4` ([kernel.org](https://github.com/mricon/patatt)) have run
|
|
261
|
-
this
|
|
262
|
-
no
|
|
263
|
-
the
|
|
301
|
+
this shape — the contributor self-signs a patch, the recipient verifies independently, no CA,
|
|
302
|
+
no server — on the Linux kernel's mailing list for years. They differ from SCPE in where the
|
|
303
|
+
key comes from: `patatt` keeps a keyring in the repository, so the project curates who may
|
|
304
|
+
sign; SCPE reads the keys the forge already publishes for the account, so there is nothing to
|
|
305
|
+
curate and nothing to depend on but the forge. Same shape, different trust root, applied to
|
|
306
|
+
the pull-request boundary instead of a mailing list.
|
|
264
307
|
|
|
265
308
|
## Status
|
|
266
309
|
|
|
267
|
-
**v0.2 — early.** This is a specification plus a reference implementation (a single-file
|
|
310
|
+
**v0.2.2 — early.** This is a specification plus a reference implementation (a single-file
|
|
268
311
|
verifier, a producer, and a maintainer-side Action). The full test suite — including a 100-PR
|
|
269
312
|
stress proof and a local end-to-end — runs on every push; the CI badge above is its live
|
|
270
313
|
result. Two more independent verifiers, in Go and Rust, reach the same verdict as the Python
|
|
@@ -278,7 +321,7 @@ accept. There is **no external adoption yet**. It is not a hosted service and ne
|
|
|
278
321
|
**`v0.1.x` is legacy: its signed-envelope path verifies a format that is not in this spec.** The
|
|
279
322
|
Action at those tags checked a second envelope format that shipped inside the package and was
|
|
280
323
|
deleted in `0.2.0`. A workflow still pinned there does not fail — it keeps posting seals for a
|
|
281
|
-
format nothing here produces
|
|
324
|
+
format nothing here produces — and now that `0.2.x` is on PyPI it breaks outright instead, because that
|
|
282
325
|
Action resolved its verifier from the package index at run time rather than from its own
|
|
283
326
|
checkout. [CHANGELOG.md](CHANGELOG.md) has what moved; [docs/MIGRATION.md](docs/MIGRATION.md)
|
|
284
327
|
has what to do about it.
|