@better-internet/oss-verify 0.1.0-draft
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.
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/cli.mjs +2 -0
- package/dist/spec/SPEC.md +329 -0
- package/dist/spec/ci-providers.json +95 -0
- package/dist/spec/contexts/v1/oss-verified.jsonld +37 -0
- package/dist/spec/models.json +82 -0
- package/dist/spec/schemas/predicate.schema.json +138 -0
- package/dist/src/checks/blobs.js +112 -0
- package/dist/src/checks/llm-audit.js +207 -0
- package/dist/src/checks/osi-license.js +115 -0
- package/dist/src/checks/reuse.js +78 -0
- package/dist/src/checks/sbom/cargo.js +124 -0
- package/dist/src/checks/sbom/go.js +137 -0
- package/dist/src/checks/sbom/javascript.js +125 -0
- package/dist/src/checks/sbom/python.js +240 -0
- package/dist/src/checks/sbom/types.js +10 -0
- package/dist/src/checks/sbom.js +173 -0
- package/dist/src/cli.mjs +225 -0
- package/dist/src/git.js +27 -0
- package/dist/src/hash.js +2 -0
- package/dist/src/predicate.js +35 -0
- package/dist/src/types.js +2 -0
- package/package.json +56 -0
- package/spec/SPEC.md +329 -0
- package/spec/ci-providers.json +95 -0
- package/spec/contexts/v1/oss-verified.jsonld +37 -0
- package/spec/models.json +82 -0
- package/spec/schemas/predicate.schema.json +138 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { sha256Hex } from "./hash.js";
|
|
5
|
+
export const PREDICATE_TYPE_URI = "https://oss-verified.better-internet.org/predicate/v1";
|
|
6
|
+
export function cliVersion() {
|
|
7
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
8
|
+
try {
|
|
9
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
10
|
+
return pkg.version || "0.0.0";
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return "0.0.0";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function cliSha() {
|
|
17
|
+
// Hash of the running CLI source bundle. For dev runs, hash this file's
|
|
18
|
+
// directory contents would be impractical — placeholder until we ship a
|
|
19
|
+
// compiled binary whose sha256 we can capture via the build step.
|
|
20
|
+
return sha256Hex(`oss-verify@${cliVersion()}`).padEnd(64, "0").slice(0, 64);
|
|
21
|
+
}
|
|
22
|
+
export function buildPredicate(input) {
|
|
23
|
+
return {
|
|
24
|
+
commit_sha: input.commitSha,
|
|
25
|
+
repo_url: input.repoUrl,
|
|
26
|
+
default_branch: input.defaultBranch,
|
|
27
|
+
criteria: input.criteria,
|
|
28
|
+
evidence: input.evidence,
|
|
29
|
+
model_id: input.modelId,
|
|
30
|
+
prompt_hash: input.promptHash,
|
|
31
|
+
cli_version: cliVersion(),
|
|
32
|
+
cli_sha: cliSha(),
|
|
33
|
+
attested_at: new Date().toISOString(),
|
|
34
|
+
};
|
|
35
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-internet/oss-verify",
|
|
3
|
+
"version": "0.1.0-draft",
|
|
4
|
+
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"description": "Forge-agnostic CLI for the oss-verified badge: deterministic open-source-licensing checks + LLM audit + Sigstore-keyless attestation.",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/Better-Internet-Org/oss-verify.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://oss-verified.better-internet.org",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sigstore",
|
|
18
|
+
"sbom",
|
|
19
|
+
"spdx",
|
|
20
|
+
"osi",
|
|
21
|
+
"verifiable-credentials",
|
|
22
|
+
"did",
|
|
23
|
+
"in-toto"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"oss-verify": "./dist/cli.mjs"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist/",
|
|
33
|
+
"spec/",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"spdx-expression-parse": "^4.0.0",
|
|
39
|
+
"spdx-license-ids": "^3.0.20"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@biomejs/biome": "^1.9.4",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"@types/spdx-expression-parse": "^3.0.5",
|
|
45
|
+
"tsx": "^4.19.0",
|
|
46
|
+
"typescript": "^5.7.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"dev": "node --experimental-strip-types src/cli.ts",
|
|
50
|
+
"build": "node scripts/build.mjs",
|
|
51
|
+
"build:binaries": "bash scripts/build-binaries.sh",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"lint": "biome check .",
|
|
54
|
+
"format": "biome check --write ."
|
|
55
|
+
}
|
|
56
|
+
}
|
package/spec/SPEC.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# oss-verified — Specification
|
|
2
|
+
|
|
3
|
+
**Version:** 0.1.0-draft
|
|
4
|
+
**Status:** Draft for public comment. This is the public-vendored copy; the design plan that motivates it is held internally and not publicly published.
|
|
5
|
+
**Editor:** TBD
|
|
6
|
+
**Issuer DID (production):** `did:web:oss-verified.better-internet.org`
|
|
7
|
+
**Issuer DID (staging):** `did:web:oss-verified-staging.better-internet.org`
|
|
8
|
+
**Controller:** `did:web:better-internet.org`
|
|
9
|
+
|
|
10
|
+
This document specifies the criteria, evidence, and verification procedures for the `oss-verified` badge — a verifiable claim that, as of a specific commit SHA, a software project meets four narrowly-scoped open-source criteria.
|
|
11
|
+
|
|
12
|
+
**This document is normative.** The internal design plan is informative only and not publicly available; where any reference to it would matter, this document carries the binding text.
|
|
13
|
+
|
|
14
|
+
## Conformance
|
|
15
|
+
|
|
16
|
+
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in [BCP 14](https://www.rfc-editor.org/info/bcp14) ([RFC 2119](https://www.rfc-editor.org/rfc/rfc2119), [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174)) when, and only when, they appear in all capitals, as shown here.
|
|
17
|
+
|
|
18
|
+
## 1. Scope
|
|
19
|
+
|
|
20
|
+
### 1.1 The claim
|
|
21
|
+
|
|
22
|
+
The badge asserts, as of commit SHA `S` of repository `R`, that **all four** of the following hold:
|
|
23
|
+
|
|
24
|
+
1. **REUSE compliance.** Every source file in the tree at `S` carries a machine-readable SPDX licensing record per [REUSE Specification v3.0](https://reuse.software/spec/) or later.
|
|
25
|
+
2. **OSI-approved declared license.** The project's primary declared license is on the [OSI-approved list](https://opensource.org/licenses) at the time of attestation. SPDX expression matches an OSI identifier exactly; aliasing or "OSI-equivalent" custom licenses are NOT accepted.
|
|
26
|
+
3. **OSI-only dependency tree.** The SBOM generated at `S` contains only OSI-approved licenses for direct and transitive runtime dependencies. Build-only and dev-only dependencies are out of scope (see §3.3).
|
|
27
|
+
4. **No proprietary blobs.** The working tree at `S` contains no proprietary binary blobs, minified-without-source bundles, or obfuscated build artifacts (see §3.4 for the precise tests).
|
|
28
|
+
|
|
29
|
+
### 1.2 Out of scope
|
|
30
|
+
|
|
31
|
+
The badge does **not** assert any of:
|
|
32
|
+
|
|
33
|
+
- Reproducible builds.
|
|
34
|
+
- Software security posture, vulnerability status, or code quality.
|
|
35
|
+
- Project governance, maintainership, funding, or community health.
|
|
36
|
+
- License *compatibility* across the SBOM (only that each license is OSI-approved).
|
|
37
|
+
- That the project is "good," "production-ready," or "fit for purpose."
|
|
38
|
+
|
|
39
|
+
Issuers, verifiers, and observers MUST NOT extend the meaning of the badge beyond §1.1.
|
|
40
|
+
|
|
41
|
+
## 2. Terminology
|
|
42
|
+
|
|
43
|
+
| Term | Meaning |
|
|
44
|
+
|---|---|
|
|
45
|
+
| **Issuer** | The program operator. The entity controlling the issuer DID. |
|
|
46
|
+
| **Maintainer** | A project owner running the CLI in their CI to attest their project. |
|
|
47
|
+
| **Verifier** | Anyone — typically a viewer of the badge — confirming the claim. |
|
|
48
|
+
| **Subject** | The repository + commit SHA being attested. |
|
|
49
|
+
| **CLI** | The `oss-verify` command-line tool. Lives at <https://github.com/better-internet-org/oss-verify> (public, MIT). |
|
|
50
|
+
| **Predicate** | The in-toto predicate JSON object (see §5) signed by Sigstore. |
|
|
51
|
+
| **Attestation** | A Sigstore in-toto attestation: predicate + DSSE envelope, recorded in Rekor. |
|
|
52
|
+
| **Credential / VC** | A W3C Verifiable Credential (Data Model 2.0) signed by the issuer DID, with custom type `OssVerifiedCredential`. |
|
|
53
|
+
| **Rekor** | The Sigstore public transparency log. |
|
|
54
|
+
| **Forge** | A git hosting provider — github.com, gitlab.com, codeberg.org, etc. |
|
|
55
|
+
| **Allowlist** | A program-published JSON document enumerating accepted CI providers ([`ci-providers.json`](./ci-providers.json)) or LLM models ([`models.json`](./models.json)). |
|
|
56
|
+
|
|
57
|
+
## 3. The four criteria
|
|
58
|
+
|
|
59
|
+
### 3.1 REUSE compliance
|
|
60
|
+
|
|
61
|
+
The CLI MUST run a REUSE lint at `S` against the v3.0 or later spec. Pass = `reuse lint` exits 0 with no `MISSING:` records and no `Bad licenses` records.
|
|
62
|
+
|
|
63
|
+
Files exempted from REUSE coverage MUST be enumerated in `.reuse/dep5` or `REUSE.toml`. Wholesale exemption (e.g. `*` glob) MUST cause the criterion to fail; reviewers MAY ignore narrow exemptions for fixtures, generated files, or third-party content provided each carries a separate SPDX record.
|
|
64
|
+
|
|
65
|
+
### 3.2 OSI-approved declared license
|
|
66
|
+
|
|
67
|
+
Pass = the SPDX expression in the project's primary license declaration, parsed per [SPDX 2.3](https://spdx.github.io/spdx-spec/v2.3/), resolves to one or more OSI-approved identifiers as listed at <https://api.opensource.org/licenses/>. The CLI MUST query the OSI API at attestation time and record the API response hash in the predicate's `evidence.osi_response_hash` field.
|
|
68
|
+
|
|
69
|
+
Compound expressions (`A OR B`, `A AND B`, `A WITH exception`) are accepted iff every leaf identifier is OSI-approved. License exceptions MUST be SPDX-recognised exception identifiers.
|
|
70
|
+
|
|
71
|
+
### 3.3 OSI-only dependency tree
|
|
72
|
+
|
|
73
|
+
The CLI MUST generate an SBOM in [SPDX 2.3](https://spdx.github.io/spdx-spec/v2.3/) or [CycloneDX 1.5+](https://cyclonedx.org/specification/overview/) format covering all direct and transitive **runtime** dependencies. Build-only and dev-only dependencies are out of scope and MUST be excluded from the SBOM scan.
|
|
74
|
+
|
|
75
|
+
The CLI MUST verify that every package in the SBOM declares at least one OSI-approved license. Packages with no license declaration MUST cause the criterion to fail; the maintainer's remedy is to upstream the license metadata or pin to a version that has it.
|
|
76
|
+
|
|
77
|
+
The hash of the SBOM SHALL be recorded in the predicate's `evidence.sbom_hash` field. Reviewers retrieving an attestation MUST be able to reproduce the SBOM by running the CLI on the same SHA with the same `cli_version`.
|
|
78
|
+
|
|
79
|
+
### 3.4 No proprietary blobs
|
|
80
|
+
|
|
81
|
+
The CLI MUST flag, and the criterion fails if any flagged file is present and not exempted, files matching:
|
|
82
|
+
|
|
83
|
+
- **Binary entropy ≥ 7.5 bits/byte** for files >100 KB and not declared in `.gitattributes` as `binary` with a SPDX licensing record.
|
|
84
|
+
- **Minified JavaScript/CSS** without an accompanying source file or sourcemap (heuristic: file size >10 KB AND average line length >500 chars).
|
|
85
|
+
- **Stripped/obfuscated build artifacts** detected via filename pattern (`*.min.js` without sourcemap, `vendor.js` without manifest, packed binaries, etc.).
|
|
86
|
+
- **Vendored proprietary archives** (filename or magic bytes of `.dll`, `.so`, `.dylib`, `.exe`, proprietary container formats) without OSI-licensed source counterparts in-tree.
|
|
87
|
+
|
|
88
|
+
Maintainers MAY exempt files via a top-level `.oss-verified.toml` listing path globs and a justification string per file. Exemptions are recorded verbatim in `predicate.evidence.exemptions` and surfaced on the verify page; reviewers and end users see and can challenge them.
|
|
89
|
+
|
|
90
|
+
## 4. Pipeline
|
|
91
|
+
|
|
92
|
+
The CLI runs the following stages in order. **All deterministic stages (§3.1–3.4) MUST pass independently.** The LLM stage (§7) MAY block but MUST NOT be relied on to grant.
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
1. Deterministic checks (§3.1 → §3.4) ← MUST all pass
|
|
96
|
+
2. LLM audit pass (§7) ← MAY block, MUST NOT grant
|
|
97
|
+
3. Build in-toto predicate (§5)
|
|
98
|
+
4. cosign attest → Sigstore (§6)
|
|
99
|
+
5. Bundle published to Rekor
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The CLI MUST refuse to produce a predicate if any deterministic stage fails. The CLI MUST refuse to produce a predicate if the LLM audit blocks. The CLI MUST NOT produce a predicate from any other code path; in particular, there is no `--force` flag, no `--skip-llm` flag, and no environment-variable override.
|
|
103
|
+
|
|
104
|
+
## 5. In-toto predicate
|
|
105
|
+
|
|
106
|
+
Schema: [`./schemas/predicate.schema.json`](./schemas/predicate.schema.json).
|
|
107
|
+
|
|
108
|
+
Predicate type URI: `https://oss-verified.better-internet.org/predicate/v1`.
|
|
109
|
+
|
|
110
|
+
Required fields:
|
|
111
|
+
|
|
112
|
+
| Field | Type | Source |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `commit_sha` | string (40 hex chars) | git rev-parse HEAD |
|
|
115
|
+
| `repo_url` | string (URL) | CI's OIDC claims (cross-checked against `git remote`) |
|
|
116
|
+
| `criteria` | object — pass/fail per `reuse`, `osi_license`, `dependency_licenses`, `no_proprietary_blobs` | CLI |
|
|
117
|
+
| `evidence` | object — `osi_response_hash`, `sbom_hash`, `sbom_format`, `sbom_uri`, `exemptions[]` | CLI |
|
|
118
|
+
| `model_id` | string | from `models.json` allowlist |
|
|
119
|
+
| `prompt_hash` | string (sha256 hex) | hash of the audit prompt template+repo content envelope |
|
|
120
|
+
| `cli_version` | string (semver) | CLI's compiled-in version |
|
|
121
|
+
| `cli_sha` | string (sha256 hex) | hash of the CLI binary that produced the attestation |
|
|
122
|
+
|
|
123
|
+
The Sigstore attestation subject MUST be the commit SHA of the repository's default branch HEAD at attestation time, expressed per the [in-toto Statement format](https://github.com/in-toto/attestation/blob/main/spec/v1/statement.md):
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"subject": [{
|
|
128
|
+
"name": "git+https://github.com/<owner>/<repo>",
|
|
129
|
+
"digest": { "gitCommit": "<S>" }
|
|
130
|
+
}]
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 6. CI provider requirements
|
|
135
|
+
|
|
136
|
+
The CLI MUST sign the predicate via `cosign attest --type custom --predicate ...` against the public Sigstore production Fulcio instance, using OIDC keyless signing rooted in the surrounding CI's OIDC identity.
|
|
137
|
+
|
|
138
|
+
The accepted OIDC issuer set is published in [`./ci-providers.json`](./ci-providers.json). Every issuer in this list MUST be on Fulcio's public OIDC allowlist. The verify endpoint MUST reject any attestation whose Fulcio certificate's OIDC issuer is not in `ci-providers.json`.
|
|
139
|
+
|
|
140
|
+
There is no BYO-key path. There is no self-hosted Fulcio path. Maintainers who require either are not served by this badge.
|
|
141
|
+
|
|
142
|
+
Updates to `ci-providers.json` follow §12.2.
|
|
143
|
+
|
|
144
|
+
## 7. LLM audit requirements
|
|
145
|
+
|
|
146
|
+
### 7.1 Purpose
|
|
147
|
+
|
|
148
|
+
The LLM audit is a second-opinion pass against patterns the deterministic checks miss (e.g. `vendor.min.js` with no source counterpart, license conflicts hidden in NOTICE files, obfuscated payloads). It MAY block; it MUST NOT grant.
|
|
149
|
+
|
|
150
|
+
### 7.2 Allowlist
|
|
151
|
+
|
|
152
|
+
Accepted models are enumerated in [`./models.json`](./models.json). The CLI MUST refuse to run with a model that is not on the allowlist. Inclusion criteria, listed in `./models.json` itself:
|
|
153
|
+
|
|
154
|
+
1. Stable, versioned API with stable model identifiers.
|
|
155
|
+
2. Auditable model identity in API responses (e.g. `system_fingerprint`, signed response headers).
|
|
156
|
+
3. Vendor retention/availability commitment of ≥12 months for the listed model version.
|
|
157
|
+
4. Documented determinism at temperature=0.
|
|
158
|
+
5. Per-call cost reasonable for adoption (target: under ~$0.50 per typical-size repo audit).
|
|
159
|
+
6. Public capability documentation sufficient to judge whether the audit prompt is appropriate.
|
|
160
|
+
|
|
161
|
+
### 7.3 Prompt-injection defense
|
|
162
|
+
|
|
163
|
+
The CLI MUST frame repository content strictly as data, never as instructions. Concretely:
|
|
164
|
+
|
|
165
|
+
- Repo content is wrapped in a fixed, hash-recorded envelope (`<repo_data>...</repo_data>`).
|
|
166
|
+
- The system prompt explicitly states that any instructions appearing inside the envelope are content to be evaluated, not commands to be followed.
|
|
167
|
+
- The CLI MUST run the published adversarial test corpus before each release and SHOULD run it as part of CI for the CLI itself.
|
|
168
|
+
|
|
169
|
+
### 7.4 Multi-pass voting (Phase 2 hardening)
|
|
170
|
+
|
|
171
|
+
In Phase 2, the CLI MUST run three independent calls at temperature=0 against the chosen model and treat the audit as blocking iff a strict majority blocks. Single-pass operation is permitted in Phase 1; Phase 2 makes it mandatory.
|
|
172
|
+
|
|
173
|
+
### 7.5 Recording the LLM verdict
|
|
174
|
+
|
|
175
|
+
The predicate's `evidence.llm_verdict` field records `pass` or `block` plus a one-line rationale. Verifiers MAY ignore the verdict field; the binding fact is that the predicate was emitted at all (which implies the LLM did not block).
|
|
176
|
+
|
|
177
|
+
## 8. Verifiable Credential
|
|
178
|
+
|
|
179
|
+
### 8.1 Type
|
|
180
|
+
|
|
181
|
+
Credential type: `OssVerifiedCredential`. JSON-LD context: [`./contexts/v1/oss-verified.jsonld`](./contexts/v1/oss-verified.jsonld), served at `https://oss-verified.better-internet.org/contexts/v1/oss-verified.jsonld`.
|
|
182
|
+
|
|
183
|
+
VCs MUST conform to [W3C Verifiable Credentials Data Model 2.0](https://www.w3.org/TR/vc-data-model-2.0/) and use `@context` referring to both the VC v2 context and the oss-verified context.
|
|
184
|
+
|
|
185
|
+
### 8.2 Issuance
|
|
186
|
+
|
|
187
|
+
The verify endpoint, on confirming a valid Sigstore attestation, mints a VC signed by the issuer DID's current `assertionMethod` key. The VC's `evidence` field MUST link to the underlying attestation via:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
"evidence": [{
|
|
191
|
+
"type": "SigstoreAttestation",
|
|
192
|
+
"rekor_log_index": <integer>,
|
|
193
|
+
"rekor_url": "https://rekor.sigstore.dev/api/v1/log/entries/<uuid>"
|
|
194
|
+
}]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The verify endpoint MUST NOT mint a VC without re-validating the Sigstore signature, the Rekor inclusion proof, the OIDC issuer (per §6), and the predicate-subject-to-repo binding for every issuance — including refreshes of an already-known repo.
|
|
198
|
+
|
|
199
|
+
### 8.3 No `validUntil`
|
|
200
|
+
|
|
201
|
+
VCs SHOULD NOT carry a `validUntil` field. Freshness is communicated via SHA-staleness on the verify page; an explicit expiry would create a redundant, less informative signal.
|
|
202
|
+
|
|
203
|
+
### 8.4 Subject
|
|
204
|
+
|
|
205
|
+
`credentialSubject` MUST include the canonical repo URL and the attested commit SHA. The credential ID MUST be a stable URL of the form `https://oss-verified.better-internet.org/credentials/{forge}/{owner}/{repo}#{commit_sha}`.
|
|
206
|
+
|
|
207
|
+
## 9. DID document
|
|
208
|
+
|
|
209
|
+
### 9.1 `did:web:` resolution
|
|
210
|
+
|
|
211
|
+
The DID document MUST be served at `https://<did-host>/.well-known/did.json` per [`did:web` v1.0](https://w3c-ccg.github.io/did-method-web/). For the production issuer, that is `https://oss-verified.better-internet.org/.well-known/did.json`.
|
|
212
|
+
|
|
213
|
+
The document MUST declare:
|
|
214
|
+
- `controller: did:web:better-internet.org`
|
|
215
|
+
- One or more `verificationMethod` entries (Ed25519 or P-256 RECOMMENDED).
|
|
216
|
+
- `assertionMethod` referencing the currently-active signing key.
|
|
217
|
+
- `service` entries for the credential endpoint and the JSON-LD context endpoint.
|
|
218
|
+
|
|
219
|
+
### 9.2 Key rotation
|
|
220
|
+
|
|
221
|
+
- **Cadence:** annual + on-incident.
|
|
222
|
+
- **Mechanism:** add the new key to `verificationMethod` first. Wait at least 24 hours so resolver caches refresh. Then begin signing new VCs with the new key. Move the new key to `assertionMethod`.
|
|
223
|
+
- **Retention:** retired keys MUST remain in `verificationMethod` indefinitely so historical VCs continue to verify. Retired keys MUST NOT appear in `assertionMethod`.
|
|
224
|
+
- **Compromise:** rotate immediately, publish an incident note at `/incidents/<date>`, re-issue currently-active VCs under the new key.
|
|
225
|
+
|
|
226
|
+
Key removal MUST NOT be used as a per-credential revocation mechanism. Per-credential revocation is §10.3.
|
|
227
|
+
|
|
228
|
+
## 10. Revocation
|
|
229
|
+
|
|
230
|
+
### 10.1 Auto-stale
|
|
231
|
+
|
|
232
|
+
The verify endpoint MUST report status `stale` when the attested SHA is not the current default-branch HEAD of the repo. Stale ≠ revoked. The maintainer's remedy is to re-run the CLI and produce a new attestation.
|
|
233
|
+
|
|
234
|
+
The verify endpoint SHOULD poll the forge for new HEAD SHAs no more often than once per 5 minutes per repo.
|
|
235
|
+
|
|
236
|
+
### 10.2 Auto-revoke
|
|
237
|
+
|
|
238
|
+
If the maintainer re-runs the CLI on a new SHA and the run **fails** the deterministic checks, the verify endpoint MUST:
|
|
239
|
+
|
|
240
|
+
1. Mark the previous VC's status as revoked via §10.3.
|
|
241
|
+
2. Render the badge SVG as red ("revoked").
|
|
242
|
+
3. Record the revocation reason as the failing criterion identifier.
|
|
243
|
+
|
|
244
|
+
Auto-revoke is irrevocable for the SHA that triggered it; future SHAs may pass and produce a new attestation, but the failing SHA's record remains.
|
|
245
|
+
|
|
246
|
+
### 10.3 BitstringStatusList
|
|
247
|
+
|
|
248
|
+
Per-credential revocation MUST use [W3C Bitstring Status List](https://www.w3.org/TR/vc-bitstring-status-list/). The status list is served at `https://<issuer>/status/v1/list.jsonld` and is itself a signed VC.
|
|
249
|
+
|
|
250
|
+
Each VC includes:
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
"credentialStatus": {
|
|
254
|
+
"id": "https://oss-verified.better-internet.org/status/v1/list.jsonld#<index>",
|
|
255
|
+
"type": "BitstringStatusListEntry",
|
|
256
|
+
"statusPurpose": "revocation",
|
|
257
|
+
"statusListIndex": "<index>",
|
|
258
|
+
"statusListCredential": "https://oss-verified.better-internet.org/status/v1/list.jsonld"
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Revoking a VC means flipping its bit in the list and re-signing the list VC. The DID signing key is **not** removed; only the bit changes.
|
|
263
|
+
|
|
264
|
+
### 10.4 Manual revoke
|
|
265
|
+
|
|
266
|
+
Manual revocation is reviewer-gated and reserved for cases the automated pipeline cannot detect: trademark disputes, fraud, post-issuance license changes affecting earlier SHAs, etc. The reviewer process is defined in Phase 3 of the plan and is out of scope for this version of the SPEC.
|
|
267
|
+
|
|
268
|
+
## 11. Verification procedure
|
|
269
|
+
|
|
270
|
+
A verifier with only the credential URL `C = https://oss-verified.better-internet.org/credentials/{forge}/{owner}/{repo}.jsonld` MUST be able to:
|
|
271
|
+
|
|
272
|
+
1. **Fetch** `C` and parse it as a JSON-LD VC.
|
|
273
|
+
2. **Resolve** the issuer DID (`did:web:oss-verified.better-internet.org`) by fetching `https://oss-verified.better-internet.org/.well-known/did.json`.
|
|
274
|
+
3. **Verify** the VC signature against the DID document's currently-active `assertionMethod` key (or, for historical VCs, any key in `verificationMethod`).
|
|
275
|
+
4. **Fetch** the linked Sigstore attestation via the `evidence[0].rekor_url` field.
|
|
276
|
+
5. **Verify** the Sigstore signature, Rekor inclusion proof, and that the Fulcio certificate's OIDC issuer is in [`./ci-providers.json`](./ci-providers.json).
|
|
277
|
+
6. **Verify** the predicate's `repo_url` matches the credential's `credentialSubject.repository`.
|
|
278
|
+
7. **Check status** by fetching the BitstringStatusList VC referenced in `credentialStatus` and reading the bit at `statusListIndex`.
|
|
279
|
+
|
|
280
|
+
A reference verifier implementation will live at `./reference-verifier/`. Conformant third-party verifiers MUST perform all seven steps above.
|
|
281
|
+
|
|
282
|
+
## 12. Conformance
|
|
283
|
+
|
|
284
|
+
### 12.1 Conformant CLI
|
|
285
|
+
|
|
286
|
+
A CLI is conformant iff it:
|
|
287
|
+
|
|
288
|
+
- Implements §3, §4, §7, and §5 (predicate) without divergence.
|
|
289
|
+
- Refuses to operate against a model not in [`./models.json`](./models.json).
|
|
290
|
+
- Refuses to attest in a CI environment whose OIDC issuer is not in [`./ci-providers.json`](./ci-providers.json).
|
|
291
|
+
- Records its own `cli_version` and `cli_sha` in the predicate.
|
|
292
|
+
|
|
293
|
+
### 12.2 Conformant verifier
|
|
294
|
+
|
|
295
|
+
A verifier is conformant iff it performs all seven steps in §11 and refuses to display a "verified" status when any step fails.
|
|
296
|
+
|
|
297
|
+
### 12.3 Allowlist updates
|
|
298
|
+
|
|
299
|
+
Updates to `models.json` and `ci-providers.json` are public PRs against the spec repo with a **14-day comment window**. Removals (e.g., vendor deprecations, security incidents) are immediate; previously-issued attestations referencing a removed entry retain their original validity window but cannot be **refreshed** against the removed entry.
|
|
300
|
+
|
|
301
|
+
## 13. References
|
|
302
|
+
|
|
303
|
+
- BCP 14 / RFC 2119 / RFC 8174 — Key word interpretation.
|
|
304
|
+
- [REUSE Specification v3.0](https://reuse.software/spec/) — File-level licensing records.
|
|
305
|
+
- [SPDX 2.3](https://spdx.github.io/spdx-spec/v2.3/) — License expression and SBOM format.
|
|
306
|
+
- [CycloneDX 1.5](https://cyclonedx.org/specification/overview/) — Alternative SBOM format.
|
|
307
|
+
- [OSI License List API](https://api.opensource.org/licenses/) — OSI-approved identifiers.
|
|
308
|
+
- [in-toto Attestation Framework v1.0](https://github.com/in-toto/attestation/) — Predicate envelope.
|
|
309
|
+
- [Sigstore](https://www.sigstore.dev/) — Keyless signing and Rekor transparency log.
|
|
310
|
+
- [W3C Verifiable Credentials Data Model 2.0](https://www.w3.org/TR/vc-data-model-2.0/).
|
|
311
|
+
- [W3C Bitstring Status List](https://www.w3.org/TR/vc-bitstring-status-list/) — Per-VC revocation.
|
|
312
|
+
- [`did:web` Method Specification v1.0](https://w3c-ccg.github.io/did-method-web/).
|
|
313
|
+
|
|
314
|
+
## Appendix A. Open items
|
|
315
|
+
|
|
316
|
+
These are tracked but not yet specified. Each MUST be resolved before Phase 1 ships.
|
|
317
|
+
|
|
318
|
+
- A.1 Initial CI allowlist scope (full Fulcio set vs. GitHub+GitLab only on day 1).
|
|
319
|
+
- A.2 Adversarial test corpus contents and where it lives.
|
|
320
|
+
- A.3 Reviewer process (manual revoke + challenge handling) — Phase 3.
|
|
321
|
+
- A.4 LLM audit prompt template (text + hash). Currently `prompt_hash` is required in the predicate but the canonical template has not been frozen.
|
|
322
|
+
- A.5 Multi-pass voting threshold for Phase 2 (strict majority, supermajority, unanimous?).
|
|
323
|
+
- A.6 Localisation of the badge SVG and verify page.
|
|
324
|
+
|
|
325
|
+
## Changelog
|
|
326
|
+
|
|
327
|
+
| Version | Date | Notes |
|
|
328
|
+
|---|---|---|
|
|
329
|
+
| 0.1.0-draft | 2026-05-08 | Initial draft for public comment. |
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "CI provider OIDC allowlist for cosign keyless signing against Sigstore production Fulcio. See SPEC.md §6. Every active issuer MUST be on Fulcio's public OIDC allowlist (https://github.com/sigstore/fulcio/tree/main/config). Updates are public PRs with a 14-day comment window. Removals are immediate.",
|
|
3
|
+
"version": "1",
|
|
4
|
+
"updated": "2026-05-08",
|
|
5
|
+
"review_window_days": 14,
|
|
6
|
+
"trust_root": {
|
|
7
|
+
"fulcio_url": "https://fulcio.sigstore.dev",
|
|
8
|
+
"rekor_url": "https://rekor.sigstore.dev"
|
|
9
|
+
},
|
|
10
|
+
"inclusion_criteria": [
|
|
11
|
+
"Issuer is on Sigstore production Fulcio's public OIDC allowlist.",
|
|
12
|
+
"Issuer's identity claims include enough subject metadata to bind an attestation to a specific repo + workflow + commit (so the verify endpoint can cross-check against the predicate's claimed repo URL).",
|
|
13
|
+
"Issuer is operated by an entity with a published security disclosure process."
|
|
14
|
+
],
|
|
15
|
+
"providers": [
|
|
16
|
+
{
|
|
17
|
+
"name": "GitHub Actions",
|
|
18
|
+
"issuer": "https://token.actions.githubusercontent.com",
|
|
19
|
+
"forges_supported": ["github.com"],
|
|
20
|
+
"subject_pattern": "repo:<owner>/<repo>:ref:refs/heads/<branch>",
|
|
21
|
+
"status": "active",
|
|
22
|
+
"added_date": "2026-05-08",
|
|
23
|
+
"added_pr": null,
|
|
24
|
+
"notes": "Day-1 target. Cross-checks: subject claim's repo MUST equal predicate.repo_url."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "GitLab CI (gitlab.com)",
|
|
28
|
+
"issuer": "https://gitlab.com",
|
|
29
|
+
"forges_supported": ["gitlab.com"],
|
|
30
|
+
"subject_pattern": "project_path:<group>/<project>:ref_type:branch:ref:<branch>",
|
|
31
|
+
"status": "active",
|
|
32
|
+
"added_date": "2026-05-08",
|
|
33
|
+
"added_pr": null,
|
|
34
|
+
"notes": "Day-1 target. Self-hosted GitLab instances are NOT covered by this entry; each self-hosted instance would require its own allowlist entry once Fulcio trusts it."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "Buildkite",
|
|
38
|
+
"issuer": null,
|
|
39
|
+
"forges_supported": ["github.com", "gitlab.com", "bitbucket.org"],
|
|
40
|
+
"subject_pattern": null,
|
|
41
|
+
"status": "candidate",
|
|
42
|
+
"added_date": null,
|
|
43
|
+
"added_pr": null,
|
|
44
|
+
"notes": "Phase 1 candidate. Issuer URL to be verified against Fulcio's live config before activation."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "Google Cloud Build",
|
|
48
|
+
"issuer": null,
|
|
49
|
+
"forges_supported": ["github.com", "source.developers.google.com"],
|
|
50
|
+
"subject_pattern": null,
|
|
51
|
+
"status": "candidate",
|
|
52
|
+
"added_date": null,
|
|
53
|
+
"added_pr": null,
|
|
54
|
+
"notes": "Phase 1 candidate. Issuer URL to be verified before activation."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "AWS CodeBuild",
|
|
58
|
+
"issuer": null,
|
|
59
|
+
"forges_supported": ["github.com", "codecommit"],
|
|
60
|
+
"subject_pattern": null,
|
|
61
|
+
"status": "candidate",
|
|
62
|
+
"added_date": null,
|
|
63
|
+
"added_pr": null,
|
|
64
|
+
"notes": "Phase 1 candidate. Issuer URL to be verified before activation."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "Azure Pipelines",
|
|
68
|
+
"issuer": null,
|
|
69
|
+
"forges_supported": ["github.com", "dev.azure.com"],
|
|
70
|
+
"subject_pattern": null,
|
|
71
|
+
"status": "candidate",
|
|
72
|
+
"added_date": null,
|
|
73
|
+
"added_pr": null,
|
|
74
|
+
"notes": "Phase 1 candidate. Issuer URL to be verified before activation."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "Codefresh",
|
|
78
|
+
"issuer": null,
|
|
79
|
+
"forges_supported": ["github.com", "gitlab.com", "bitbucket.org"],
|
|
80
|
+
"subject_pattern": null,
|
|
81
|
+
"status": "candidate",
|
|
82
|
+
"added_date": null,
|
|
83
|
+
"added_pr": null,
|
|
84
|
+
"notes": "Phase 1 candidate. Issuer URL to be verified before activation."
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"deferred": [
|
|
88
|
+
{
|
|
89
|
+
"name": "Codeberg / Forgejo",
|
|
90
|
+
"reason": "Forgejo has not yet shipped Fulcio-trusted OIDC as of the spec date. Reconsider when the upstream feature lands.",
|
|
91
|
+
"reconsider_after": null
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"removed": []
|
|
95
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"@version": 1.1,
|
|
4
|
+
"@protected": true,
|
|
5
|
+
"ossv": "https://oss-verified.better-internet.org/vocab/v1#",
|
|
6
|
+
"xsd": "http://www.w3.org/2001/XMLSchema#",
|
|
7
|
+
|
|
8
|
+
"OssVerifiedCredential": "ossv:OssVerifiedCredential",
|
|
9
|
+
"OssVerifiedSubject": "ossv:OssVerifiedSubject",
|
|
10
|
+
"SigstoreAttestation": "ossv:SigstoreAttestation",
|
|
11
|
+
|
|
12
|
+
"repository": { "@id": "ossv:repository", "@type": "@id" },
|
|
13
|
+
"commitSha": "ossv:commitSha",
|
|
14
|
+
"forge": "ossv:forge",
|
|
15
|
+
"defaultBranch": "ossv:defaultBranch",
|
|
16
|
+
"attestedAt": { "@id": "ossv:attestedAt", "@type": "xsd:dateTime" },
|
|
17
|
+
|
|
18
|
+
"criteria": {
|
|
19
|
+
"@id": "ossv:criteria",
|
|
20
|
+
"@type": "@id",
|
|
21
|
+
"@container": "@set"
|
|
22
|
+
},
|
|
23
|
+
"predicateType": { "@id": "ossv:predicateType", "@type": "@id" },
|
|
24
|
+
"modelId": "ossv:modelId",
|
|
25
|
+
"promptHash": "ossv:promptHash",
|
|
26
|
+
"sbomHash": "ossv:sbomHash",
|
|
27
|
+
"sbomFormat": "ossv:sbomFormat",
|
|
28
|
+
"sbomUri": { "@id": "ossv:sbomUri", "@type": "@id" },
|
|
29
|
+
"cliVersion": "ossv:cliVersion",
|
|
30
|
+
"cliSha": "ossv:cliSha",
|
|
31
|
+
|
|
32
|
+
"rekorLogIndex": { "@id": "ossv:rekorLogIndex", "@type": "xsd:integer" },
|
|
33
|
+
"rekorUrl": { "@id": "ossv:rekorUrl", "@type": "@id" },
|
|
34
|
+
"fulcioCertificateChain": "ossv:fulcioCertificateChain",
|
|
35
|
+
"oidcIssuer": { "@id": "ossv:oidcIssuer", "@type": "@id" }
|
|
36
|
+
}
|
|
37
|
+
}
|
package/spec/models.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "LLM model allowlist for the oss-verified audit pass. See SPEC.md §7. Updates are public PRs with a 14-day comment window. Removals are immediate; previously-issued attestations referencing a removed model retain validity but cannot be refreshed against it.",
|
|
3
|
+
"version": "1",
|
|
4
|
+
"updated": "2026-05-08",
|
|
5
|
+
"review_window_days": 14,
|
|
6
|
+
"inclusion_criteria": [
|
|
7
|
+
"Stable, versioned API with stable model identifiers that don't silently change.",
|
|
8
|
+
"Auditable model identity in API responses (system_fingerprint, signed response headers, etc.) so verifiers can confirm the predicate's claimed model actually answered.",
|
|
9
|
+
"Vendor retention/availability commitment of >=12 months for the listed model version.",
|
|
10
|
+
"Documented determinism at temperature=0; vendor publishes the variance window or commits to deterministic outputs.",
|
|
11
|
+
"Per-call cost reasonable for adoption (target: under ~$0.50 USD per typical-size repo audit).",
|
|
12
|
+
"Public capability documentation sufficient that a reviewer can judge whether the audit prompt is appropriate for the model."
|
|
13
|
+
],
|
|
14
|
+
"models": [
|
|
15
|
+
{
|
|
16
|
+
"model_id": "claude-opus-4-7",
|
|
17
|
+
"vendor": "Anthropic",
|
|
18
|
+
"api_endpoint": "https://api.anthropic.com/v1/messages",
|
|
19
|
+
"model_identity_method": "response.model field + response headers",
|
|
20
|
+
"retention_until": "2027-05-08",
|
|
21
|
+
"determinism_doc": null,
|
|
22
|
+
"typical_cost_per_repo_usd": 0.45,
|
|
23
|
+
"status": "active",
|
|
24
|
+
"added_date": "2026-05-08",
|
|
25
|
+
"added_pr": null,
|
|
26
|
+
"notes": "Default audit model. Strongest reasoning on subtle license/blob heuristics."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"model_id": "claude-sonnet-4-6",
|
|
30
|
+
"vendor": "Anthropic",
|
|
31
|
+
"api_endpoint": "https://api.anthropic.com/v1/messages",
|
|
32
|
+
"model_identity_method": "response.model field + response headers",
|
|
33
|
+
"retention_until": "2027-05-08",
|
|
34
|
+
"determinism_doc": null,
|
|
35
|
+
"typical_cost_per_repo_usd": 0.18,
|
|
36
|
+
"status": "active",
|
|
37
|
+
"added_date": "2026-05-08",
|
|
38
|
+
"added_pr": null,
|
|
39
|
+
"notes": "Cost-balanced default for the staging environment."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"model_id": "claude-haiku-4-5",
|
|
43
|
+
"vendor": "Anthropic",
|
|
44
|
+
"api_endpoint": "https://api.anthropic.com/v1/messages",
|
|
45
|
+
"model_identity_method": "response.model field + response headers",
|
|
46
|
+
"retention_until": "2027-05-08",
|
|
47
|
+
"determinism_doc": null,
|
|
48
|
+
"typical_cost_per_repo_usd": 0.05,
|
|
49
|
+
"status": "active",
|
|
50
|
+
"added_date": "2026-05-08",
|
|
51
|
+
"added_pr": null,
|
|
52
|
+
"notes": "Lowest-cost option; recommended for very small repos or local-dry-run flows."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"model_id": "gpt-5",
|
|
56
|
+
"vendor": "OpenAI",
|
|
57
|
+
"api_endpoint": "https://api.openai.com/v1/chat/completions",
|
|
58
|
+
"model_identity_method": "response.system_fingerprint + response.model field",
|
|
59
|
+
"retention_until": "2027-05-08",
|
|
60
|
+
"determinism_doc": null,
|
|
61
|
+
"typical_cost_per_repo_usd": 0.4,
|
|
62
|
+
"status": "active",
|
|
63
|
+
"added_date": "2026-05-08",
|
|
64
|
+
"added_pr": null,
|
|
65
|
+
"notes": null
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"model_id": "gemini-2.5-pro",
|
|
69
|
+
"vendor": "Google",
|
|
70
|
+
"api_endpoint": "https://generativelanguage.googleapis.com/v1/models/gemini-2.5-pro:generateContent",
|
|
71
|
+
"model_identity_method": "response.modelVersion field",
|
|
72
|
+
"retention_until": "2027-05-08",
|
|
73
|
+
"determinism_doc": null,
|
|
74
|
+
"typical_cost_per_repo_usd": 0.35,
|
|
75
|
+
"status": "active",
|
|
76
|
+
"added_date": "2026-05-08",
|
|
77
|
+
"added_pr": null,
|
|
78
|
+
"notes": null
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"removed": []
|
|
82
|
+
}
|