cra24 1.0.0__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 (93) hide show
  1. cra24-1.0.0/.gitignore +40 -0
  2. cra24-1.0.0/CHANGELOG.md +283 -0
  3. cra24-1.0.0/CLA.md +182 -0
  4. cra24-1.0.0/LICENSE +235 -0
  5. cra24-1.0.0/LICENSING.md +76 -0
  6. cra24-1.0.0/NOTICE +61 -0
  7. cra24-1.0.0/PKG-INFO +332 -0
  8. cra24-1.0.0/README.md +291 -0
  9. cra24-1.0.0/docs/architecture.md +231 -0
  10. cra24-1.0.0/docs/article-14.md +228 -0
  11. cra24-1.0.0/docs/cli.md +380 -0
  12. cra24-1.0.0/docs/evidence.md +177 -0
  13. cra24-1.0.0/docs/feeds.md +207 -0
  14. cra24-1.0.0/docs/licensing-faq.md +206 -0
  15. cra24-1.0.0/docs/roadmap.md +184 -0
  16. cra24-1.0.0/docs/srp-field-spec.md +192 -0
  17. cra24-1.0.0/docs/triage-rules.md +262 -0
  18. cra24-1.0.0/docs/watch.md +225 -0
  19. cra24-1.0.0/pyproject.toml +107 -0
  20. cra24-1.0.0/src/cra24/__init__.py +25 -0
  21. cra24-1.0.0/src/cra24/__main__.py +10 -0
  22. cra24-1.0.0/src/cra24/cli.py +954 -0
  23. cra24-1.0.0/src/cra24/clock.py +328 -0
  24. cra24-1.0.0/src/cra24/config.py +172 -0
  25. cra24-1.0.0/src/cra24/data/annex.json +72 -0
  26. cra24-1.0.0/src/cra24/data/schemas/csaf_json_schema.json +1414 -0
  27. cra24-1.0.0/src/cra24/data/schemas/cvss-v2.0.json +8 -0
  28. cra24-1.0.0/src/cra24/data/schemas/cvss-v3.0.json +8 -0
  29. cra24-1.0.0/src/cra24/data/schemas/cvss-v3.1.json +60 -0
  30. cra24-1.0.0/src/cra24/data/schemas/openvex_json_schema.json +317 -0
  31. cra24-1.0.0/src/cra24/data/srp-fields.json +157 -0
  32. cra24-1.0.0/src/cra24/emit/__init__.py +28 -0
  33. cra24-1.0.0/src/cra24/emit/csaf.py +302 -0
  34. cra24-1.0.0/src/cra24/emit/srp.py +365 -0
  35. cra24-1.0.0/src/cra24/emit/validate.py +165 -0
  36. cra24-1.0.0/src/cra24/emit/vex.py +137 -0
  37. cra24-1.0.0/src/cra24/errors.py +61 -0
  38. cra24-1.0.0/src/cra24/evidence.py +367 -0
  39. cra24-1.0.0/src/cra24/feeds/__init__.py +40 -0
  40. cra24-1.0.0/src/cra24/feeds/base.py +389 -0
  41. cra24-1.0.0/src/cra24/feeds/cache.py +236 -0
  42. cra24-1.0.0/src/cra24/feeds/epss.py +104 -0
  43. cra24-1.0.0/src/cra24/feeds/euvd.py +177 -0
  44. cra24-1.0.0/src/cra24/feeds/kev.py +121 -0
  45. cra24-1.0.0/src/cra24/feeds/nvd.py +208 -0
  46. cra24-1.0.0/src/cra24/feeds/osv.py +242 -0
  47. cra24-1.0.0/src/cra24/feeds/registry.py +316 -0
  48. cra24-1.0.0/src/cra24/ingest/__init__.py +17 -0
  49. cra24-1.0.0/src/cra24/ingest/base.py +107 -0
  50. cra24-1.0.0/src/cra24/ingest/buildroot.py +180 -0
  51. cra24-1.0.0/src/cra24/ingest/sbom.py +181 -0
  52. cra24-1.0.0/src/cra24/ingest/yocto.py +305 -0
  53. cra24-1.0.0/src/cra24/logging.py +62 -0
  54. cra24-1.0.0/src/cra24/model.py +412 -0
  55. cra24-1.0.0/src/cra24/notify.py +310 -0
  56. cra24-1.0.0/src/cra24/purl.py +125 -0
  57. cra24-1.0.0/src/cra24/triage.py +651 -0
  58. cra24-1.0.0/src/cra24/versions.py +168 -0
  59. cra24-1.0.0/src/cra24/watch.py +338 -0
  60. cra24-1.0.0/tests/conftest.py +95 -0
  61. cra24-1.0.0/tests/fixtures/buildroot-output/build/linux-6.6.22/.config +17 -0
  62. cra24-1.0.0/tests/fixtures/buildroot-output/legal-info/manifest.csv +6 -0
  63. cra24-1.0.0/tests/fixtures/buildroot-output/pkg-stats.json +13 -0
  64. cra24-1.0.0/tests/fixtures/demo-config.json +16 -0
  65. cra24-1.0.0/tests/fixtures/feed-cache/epss/epss_scores-current.csv +6 -0
  66. cra24-1.0.0/tests/fixtures/feed-cache/epss/epss_scores-current.csv.meta +10 -0
  67. cra24-1.0.0/tests/fixtures/feed-cache/euvd/exploitedvulnerabilities.json +29 -0
  68. cra24-1.0.0/tests/fixtures/feed-cache/euvd/exploitedvulnerabilities.json.meta +10 -0
  69. cra24-1.0.0/tests/fixtures/feed-cache/kev/known_exploited_vulnerabilities.json +47 -0
  70. cra24-1.0.0/tests/fixtures/feed-cache/kev/known_exploited_vulnerabilities.json.meta +10 -0
  71. cra24-1.0.0/tests/fixtures/feed-cache/nvd/cves/CVE-2024-1086.json +58 -0
  72. cra24-1.0.0/tests/fixtures/feed-cache/nvd/cves/CVE-2024-1086.json.meta +10 -0
  73. cra24-1.0.0/tests/fixtures/feed-cache/osv/query/cd16302a59cc3cdb.json +42 -0
  74. cra24-1.0.0/tests/fixtures/feed-cache/osv/query/cd16302a59cc3cdb.json.meta +17 -0
  75. cra24-1.0.0/tests/fixtures/sbom-cyclonedx.json +32 -0
  76. cra24-1.0.0/tests/fixtures/sbom-spdx.json +23 -0
  77. cra24-1.0.0/tests/fixtures/yocto-build/tmp/deploy/images/raspberrypi4-64/config-6.6.22 +17 -0
  78. cra24-1.0.0/tests/fixtures/yocto-build/tmp/deploy/images/raspberrypi4-64/core-image-minimal.manifest +10 -0
  79. cra24-1.0.0/tests/fixtures/yocto-build/tmp/deploy/licenses/busybox/license.manifest +5 -0
  80. cra24-1.0.0/tests/fixtures/yocto-build/tmp/deploy/licenses/linux-raspberrypi/license.manifest +5 -0
  81. cra24-1.0.0/tests/fixtures/yocto-build/tmp/deploy/licenses/openssl/license.manifest +10 -0
  82. cra24-1.0.0/tests/fixtures/yocto-build/tmp/log/cve/cve-summary.json +72 -0
  83. cra24-1.0.0/tests/test_cli.py +558 -0
  84. cra24-1.0.0/tests/test_clock.py +135 -0
  85. cra24-1.0.0/tests/test_emit.py +275 -0
  86. cra24-1.0.0/tests/test_evidence.py +244 -0
  87. cra24-1.0.0/tests/test_feeds.py +362 -0
  88. cra24-1.0.0/tests/test_ingest.py +136 -0
  89. cra24-1.0.0/tests/test_model.py +142 -0
  90. cra24-1.0.0/tests/test_repo_hygiene.py +145 -0
  91. cra24-1.0.0/tests/test_triage.py +253 -0
  92. cra24-1.0.0/tests/test_versions.py +82 -0
  93. cra24-1.0.0/tests/test_watch.py +288 -0
cra24-1.0.0/.gitignore ADDED
@@ -0,0 +1,40 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ .venv/
6
+ venv/
7
+ .env
8
+
9
+ # Local working notes, scratch files and delivered artefacts. Anything in here
10
+ # is for this checkout only and must never reach a clone: keep working notes
11
+ # there rather than loose in the root, where they end up in a release tarball.
12
+ /__internal__/
13
+
14
+ # Anchored to the repository root on purpose. An unanchored `build/` also
15
+ # matches tests/fixtures/buildroot-output/build/, which silently excluded a
16
+ # kernel .config fixture and broke the suite only in a fresh clone.
17
+ /dist/
18
+ /build/
19
+
20
+ # pytest / tooling
21
+ .pytest_cache/
22
+ .ruff_cache/
23
+ .mypy_cache/
24
+ .coverage
25
+ coverage.xml
26
+ htmlcov/
27
+
28
+ # cra24 working output — never commit a customer's dossier. These contain an
29
+ # unpatched-vulnerability inventory of a shipping product.
30
+ /product.json
31
+ /dossier/
32
+ /evidence/
33
+ *.dossier/
34
+ .cra24-cache/
35
+ /watch-dossiers/
36
+ /products/
37
+ watch-state.json
38
+
39
+ # ...but the test fixtures are not working output.
40
+ !tests/fixtures/**
@@ -0,0 +1,283 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
+ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Two kinds of change get called out specially, because they affect what you can
9
+ rely on:
10
+
11
+ - **Field spec** changes track the ENISA SRP form. They can alter which fields
12
+ block a submission, so they are listed even when no code changed.
13
+ - **Triage** changes can alter the verdict for a CVE you already published a VEX
14
+ statement about. Re-run and re-publish when you see one.
15
+
16
+ ---
17
+
18
+ ## [1.0.0] — 2026-09-19
19
+
20
+ First public release.
21
+
22
+ No functional change from 0.4.0. The version says 1.0 because the surface is
23
+ now one I am willing to keep stable: the CLI verbs and their exit codes, the
24
+ shape of `product.json`, and the emitted CSAF, OpenVEX and SRP documents. Those
25
+ are what a build pipeline and an auditor come to depend on, and breaking them
26
+ quietly is worse than a major version number.
27
+
28
+ Versions 0.1.0 through 0.4.0 were developed before publication and are not
29
+ tagged in this repository. The entries below are kept because they explain why
30
+ the engine behaves as it does — particularly which conclusions it refuses to
31
+ draw, and why.
32
+
33
+ ---
34
+
35
+ ## [0.4.0] — 2026-09-19
36
+
37
+ A correctness release. Nothing here is a new capability you asked for; it is
38
+ the set of things the engine was getting quietly wrong, found by reviewing it
39
+ against its own stated contracts.
40
+
41
+ ### Triage
42
+
43
+ - **A kernel symbol that is merely *absent* no longer counts as "switched
44
+ off".** `read_kernel_config` has always recorded an explicitly unset symbol as
45
+ `n` so that "we looked and it is off" stays distinguishable from "we never
46
+ looked"; the config gate then collapsed the two. In a kconfig-generated
47
+ `.config` that was still right — every reachable symbol was considered when
48
+ the file was written — but `--kernel-config` also accepts a defconfig or a
49
+ `.cfg` fragment, which list only deltas. There, a gate symbol the file never
50
+ mentioned returned `not set in the shipped kernel config`, the verdict became
51
+ `not_affected`, and the CVE left the dossier on evidence the tool had never
52
+ actually checked.
53
+
54
+ Provenance now travels with the data: `product.json` records
55
+ `kernel_config_complete`, and the gate declines to conclude from absence
56
+ unless the config is complete.
57
+
58
+ **Re-run any dossier gated on a config fragment.** Verdicts produced from a
59
+ full `.config` are unchanged.
60
+
61
+ ### Fixed
62
+
63
+ - **Article 14(4) is a calendar month, measured from the submission.** The
64
+ incident final-report deadline used `timedelta(days=30)` and counted from the
65
+ 72-hour notification *deadline*. Regulation (EEC, Euratom) No 1182/71 counts
66
+ months to the day bearing the same number, and 14(4) runs from the submission:
67
+ a notification filed on 31 January is due 28 February, where the old
68
+ arithmetic said 2 March. Both errors moved the deadline later than the law
69
+ does. Where no submission time was recorded the 72-hour deadline is still
70
+ assumed, and the basis string now says so.
71
+ - **`cra24 doctor` no longer reports validation as available when it is not.**
72
+ It checked that `jsonschema` imported and nothing else, while the validator
73
+ also needs `referencing` and a `jsonschema` of at least 4.18. On a machine
74
+ with the distribution's jsonschema 4.10 and no `referencing` — Ubuntu 24.04,
75
+ for one — doctor printed a green line and the first `cra24 report` then died
76
+ with a bare `ModuleNotFoundError`. The check now covers what the validator
77
+ actually imports and names the missing piece.
78
+ - **Feed cache entry names are contained.** `root / feed / name` was built from
79
+ identifiers that arrive from outside — a CVE typed on the command line, but
80
+ also one read out of an SBOM or a cve-check summary — so an absolute name
81
+ replaced the path and `../` walked out of the cache directory.
82
+ - **Feed responses are bounded**, at 512 MiB before and after decompression. A
83
+ few hundred kilobytes of compressed zeros previously asked for as much memory
84
+ as it liked, on a watch that runs unattended on an hourly timer.
85
+ - **A redirect from `https` to plaintext `http` is refused** rather than
86
+ followed. The answers end up in a regulatory filing.
87
+ - A `429` or `5xx` on the final attempt no longer honours `Retry-After` before
88
+ giving up anyway.
89
+
90
+ ### Added
91
+
92
+ - **`cra24 verify --expect-head SHA256`.** Deleting records from the end of the
93
+ ledger leaves a chain that is shorter and still internally consistent, so it
94
+ verified clean — nothing inside a ledger can detect its own truncation. The
95
+ head hash was always meant to be anchored somewhere you do not control; this
96
+ makes that anchor checkable.
97
+ - **`cra24 doctor` reports how the evidence key was read** — hex or text, and
98
+ how many bytes — in red below sixteen. A value that parses as hex is decoded
99
+ as hex, so the passphrase `deadbeef` is four bytes rather than eight. That
100
+ rule is unchanged, because changing it would invalidate every ledger already
101
+ signed under it.
102
+ - `tests/test_repo_hygiene.py`, which the `licence headers` CI job has invoked
103
+ since 0.2.0 without the file ever existing — so that job failed on every push
104
+ while the check it stood for was not running. Enforces the SPDX header and
105
+ copyright line on every source file, that each agrees with the licence
106
+ `pyproject.toml` declares, and that no workflow runs a test path that is not
107
+ there.
108
+
109
+ ### Changed
110
+
111
+ - `Ledger.append()` reads the last record by seeking from the end instead of
112
+ parsing the whole ledger, so appending no longer costs the length of the
113
+ history. Ten years of hourly evidence was going to notice.
114
+ - `doctor`'s `jsonschema` line is now `validation`, since whether `jsonschema`
115
+ imports was never the thing it was reporting.
116
+
117
+ ---
118
+
119
+ ## [0.3.0] — 2026-09-18
120
+
121
+ The release that answers the hardest input: *is this actually being exploited?*
122
+
123
+ ### Added
124
+
125
+ - **Five feed clients**, all offline-capable, all using `urllib` from the
126
+ standard library so the base package still has zero runtime dependencies.
127
+
128
+ | Feed | Answers |
129
+ | --- | --- |
130
+ | CISA KEV | is this actively exploited — the Article 14(1) trigger |
131
+ | ENISA EUVD | ENISA's own exploited list, plus the EUVD id for the SRP form |
132
+ | EPSS | how likely exploitation is in 30 days, for ordering the queue |
133
+ | OSV | which versions are affected and which one fixes it |
134
+ | NVD | CVSS score and vector for the notification and final report |
135
+
136
+ - **`cra24 feeds sync` and `cra24 feeds status`.** Status names every host that
137
+ would be contacted, how old each cached copy is, and — with `--terms` — the
138
+ terms of use, all without opening a socket.
139
+ - **An on-disk cache that is a feature, not an optimisation.** Payloads are
140
+ stored byte-identical to what the server sent, with the SHA-256 in a sidecar,
141
+ so a cached artefact can be hashed and shown to be the file CISA published. A
142
+ populated cache can be tarred up on a networked machine and copied to an
143
+ air-gapped build machine, which is the normal case in this industry.
144
+ - **`--offline` everywhere**, refusing to open a socket and saying how old the
145
+ cache is rather than pretending it is current.
146
+ - **`cra24 watch`** — poll the feeds against registered products and fire when
147
+ something lands. Writes the dossier and starts the clock at the moment of
148
+ detection, so what reaches you is a draft rather than an alert. Exits 1 on a
149
+ reportable finding.
150
+ - **Notifiers**: stdout, `file:`, `webhook:` and `email:`, repeatable. SMTP
151
+ credentials come from the environment, never a config file. A reportable alert
152
+ carries `X-CRA24-Reportable: yes`.
153
+ - **A hardened systemd unit and timer** in `contrib/systemd/` — a oneshot on a
154
+ timer rather than a long-lived daemon, with `RandomizedDelaySec` so that not
155
+ every installation polls CISA on the hour.
156
+ - `docs/feeds.md` and `docs/watch.md`.
157
+
158
+ ### Changed
159
+
160
+ - `triage()` takes an optional `enrichment`. Feed data fills arguments the caller
161
+ did not supply and **never overrides one that was**: an explicit
162
+ `--actively-exploited no` is a human decision and a feed does not overturn it.
163
+ - Every fact a feed supplied appears in the evidence trail with the feed named,
164
+ so a dossier can show a verdict changed because CISA added the CVE this
165
+ morning rather than because the tool changed its mind.
166
+ - `cra24 doctor` reports which feeds are cached and whether `NVD_API_KEY` is set.
167
+
168
+ ### Fixed
169
+
170
+ - An overdue obligation rendered as "7h 51m overdue left". `_fmt_delta` already
171
+ says "overdue"; the CLI no longer appends "left" to it. Nonsense wording at
172
+ exactly the moment someone is reading carefully.
173
+
174
+ ### Triage
175
+
176
+ - **A CVE listed in CISA KEV or ENISA's exploited list is now reportable without
177
+ passing `--actively-exploited yes`.** Verdicts for such CVEs change from *not
178
+ reportable, exploitation unknown* to *reportable*. Re-run anything you assessed
179
+ before upgrading.
180
+ - **Absence from KEV is recorded as unknown, never as `False`.** KEV is
181
+ conservative, curated and US-federal; absence means CISA has not confirmed
182
+ exploitation, not that there is none. A customer reporting an attack starts the
183
+ clock regardless.
184
+ - OSV version ranges now feed rule 1, which can move a verdict to `fixed` when
185
+ the shipped version is at or past the advisory's fixed version.
186
+
187
+ ---
188
+
189
+ ## [0.2.0] — 2026-09-17
190
+
191
+ The release that turns a sketch into something you could file with.
192
+
193
+ ### Added
194
+
195
+ - **Buildroot ingest** — `legal-info/manifest.csv` for the inventory,
196
+ `pkg-stats.json` for CVE and CPE data, `<PKG>_IGNORE_CVES` read as a triage
197
+ signal.
198
+ - **Kernel configuration gating** — `--gate CONFIG_SYM`. A CVE whose vulnerable
199
+ code sits behind a symbol that is unset in the shipped `.config` is downgraded
200
+ from `affected` to `not_affected` with the
201
+ `vulnerable_code_not_in_execute_path` justification. Gating can only downgrade,
202
+ never upgrade, and the absence of gating data is never read as "not gated".
203
+ - **A real triage engine** (`triage.py`) replacing the 0.1 status lookup. Every
204
+ verdict carries a confidence level and the ordered list of rules that produced
205
+ it, each with the file it read.
206
+ - **Yocto `CVE_STATUS` reason mapping** — the reason string, not just the
207
+ patched/ignored/unpatched group, is read and mapped to the closed OpenVEX
208
+ justification vocabulary. This is where most of the false-positive reduction
209
+ lives.
210
+ - **CycloneDX `analysis.justification` mapping** — an SBOM that already carries
211
+ triage decisions no longer loses them on ingest.
212
+ - **Advisory version-range matching** — `--fixed-version`, with introduced and
213
+ last-affected bounds, using Debian ordering rules so
214
+ `1.2.3+git0+abcdef1234-r0` compares correctly against `1.2.4`.
215
+ - **Schema validation on every emit.** CSAF 2.0 and OpenVEX 0.2.0 documents are
216
+ validated against the vendored official schemas before they are written or
217
+ recorded as evidence. `--no-validate` opts out.
218
+ - **Three reporting stages**, not one: `--stage early_warning | notification |
219
+ final_report`, with later stages inheriting earlier fields.
220
+ - **Gap detection.** The dossier lists the mandatory fields that have no value,
221
+ first, before anything else. `cra24 report` exits 6 when the dossier is written
222
+ but not submittable, so a CI job can tell "not ready" from "crashed".
223
+ - **`cra24 init`** writes a configuration template with every field present.
224
+ - **`cra24 spec`** prints the SRP field set this release carries.
225
+ - **`cra24 doctor`** reports the installation: spec versions, bundled schemas,
226
+ whether validation is available, whether evidence signing is configured.
227
+ - **Optional HMAC over evidence records** via `CRA24_EVIDENCE_KEY`, raising the
228
+ bar from "an editor" to "someone who also has the key".
229
+ - **Dual licensing**: AGPL-3.0-only plus a commercial licence, with a CLA, a
230
+ commercial licence template, and an SPDX header on every source file enforced
231
+ by a test.
232
+
233
+ ### Changed
234
+
235
+ - **The SRP field set moved out of code and into
236
+ `src/cra24/data/srp-fields.json`**, versioned, with its sources and its known
237
+ disagreements recorded. When ENISA moves the form, the fix is a data edit.
238
+ - **Product serialisation is deterministic.** `scanned_at` is stamped once at
239
+ ingest instead of at save time, so saving the same inventory twice produces
240
+ identical bytes and the evidence ledger does not record a spurious change.
241
+ - **Library code no longer calls `SystemExit`.** Everything raises from the
242
+ `Cra24Error` hierarchy; only `cli.py` turns those into exit codes.
243
+ - **The evidence ledger** gained sequence numbers, artefact re-hashing on verify,
244
+ and a `summary` view. Verification now reports every problem, not just the
245
+ first.
246
+ - CSAF output models the component *inside* the product with a
247
+ `default_component_of` relationship, carries purls in product identification
248
+ helpers, and emits a flag or threat for every `known_not_affected` product.
249
+ - OpenVEX output emits components as subcomponents of the product, and always
250
+ carries either a justification or an impact statement on `not_affected`.
251
+
252
+ ### Fixed
253
+
254
+ - **A recipe whose packages ship under a different name is no longer dropped.**
255
+ `cve-check` reports at recipe granularity (`linux-raspberrypi`) while image
256
+ manifests list packages (`kernel-image-image`). The 0.1 ingest dropped the
257
+ recipe because its own name was absent from the manifest, which silently
258
+ removed every kernel CVE from the dossier. A recipe-to-package map built from
259
+ `license.manifest` fixes it. *This is the most consequential bug fixed in this
260
+ release; re-scan any 0.1 inventory.*
261
+ - Yocto `+gitAUTOINC+` version decoration is stripped correctly instead of
262
+ leaving `AUTOINC+<hash>` attached to the version.
263
+ - `-v` is accepted both before and after the subcommand.
264
+
265
+ ### Field spec
266
+
267
+ - Initial versioned spec, `2026-09-17`. Reconstructed from ENISA's SRP pages, the
268
+ Commission's reporting guidance and two public field guides. Records two known
269
+ disagreements: whether severity is mandatory at 24 hours, and whether the
270
+ affected Member States field is "required" or "required if available".
271
+
272
+ ---
273
+
274
+ ## [0.1.0] — 2026-09-17
275
+
276
+ Initial sketch: Yocto and SBOM ingest, a flat SRP field list, CSAF and OpenVEX
277
+ emit, a hash-chained evidence ledger.
278
+
279
+ [1.0.0]: https://github.com/cra24/cra24/releases/tag/v1.0.0
280
+ [0.4.0]: https://github.com/cra24/cra24/releases/tag/v0.4.0
281
+ [0.3.0]: https://github.com/cra24/cra24/releases/tag/v0.3.0
282
+ [0.2.0]: https://github.com/cra24/cra24/releases/tag/v0.2.0
283
+ [0.1.0]: https://github.com/cra24/cra24/releases/tag/v0.1.0
cra24-1.0.0/CLA.md ADDED
@@ -0,0 +1,182 @@
1
+ # Contributor Licence Agreement
2
+
3
+ **Version 1.0, 17 September 2026.**
4
+
5
+ > **Not legal advice.** Adapted from the widely used Apache-style ICLA/CCLA
6
+ > pattern, rewritten for a dual-licensed project. Have a lawyer review it before
7
+ > you rely on it. Placeholders are marked `[LIKE THIS]`.
8
+
9
+ ---
10
+
11
+ ## Why cra24 asks for this
12
+
13
+ cra24 is offered under two licences: the AGPL-3.0, and a paid commercial licence
14
+ (see `LICENSING.md`). Only the copyright holder can offer a work under a licence
15
+ other than the one it was received under.
16
+
17
+ If a contribution arrived under the AGPL alone, the project could no longer grant
18
+ the commercial licence over code containing it — one patch would quietly end the
19
+ business model, and with it the reason anyone is paid to maintain this.
20
+
21
+ So contributors grant a parallel licence. **You keep your copyright.** You are not
22
+ assigning anything. You are granting the project the right to distribute your
23
+ contribution under both licences, alongside your own unchanged right to do
24
+ whatever else you want with your own code.
25
+
26
+ If that trade is not one you want to make, that is a legitimate position. Open an
27
+ issue describing the change instead; a maintainer can implement it independently.
28
+
29
+ ---
30
+
31
+ ## Individual Contributor Licence Agreement
32
+
33
+ You accept and agree to the following terms for your past, present and future
34
+ Contributions to cra24. Except for the licences granted here, you reserve all
35
+ right, title and interest in and to your Contributions.
36
+
37
+ ### 1. Definitions
38
+
39
+ **"You"** means the copyright owner, or the legal entity authorised by the
40
+ copyright owner, entering into this agreement.
41
+
42
+ **"Contribution"** means any original work of authorship, including any
43
+ modification of an existing work, that is intentionally submitted by You to the
44
+ Project for inclusion in, or documentation of, any of its products.
45
+
46
+ **"Submitted"** means any form of electronic, verbal or written communication sent
47
+ to the Project or its maintainers, including on source code repositories, issue
48
+ trackers and mailing lists, but excluding communication conspicuously marked in
49
+ writing as **"Not a Contribution."**
50
+
51
+ **"Project"** means the cra24 project, and `Amr Ben Abdessalem (France)` as its steward.
52
+
53
+ ### 2. Copyright licence
54
+
55
+ You grant to the Project and to recipients of software distributed by the Project
56
+ a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
57
+ copyright licence to reproduce, prepare derivative works of, publicly display,
58
+ publicly perform, sublicense and distribute Your Contributions and such derivative
59
+ works.
60
+
61
+ **This licence expressly includes the right to sublicense Your Contribution under
62
+ terms of the Project's choosing, including proprietary commercial terms**, as part
63
+ of the dual-licensing model described in `LICENSING.md`.
64
+
65
+ ### 3. Patent licence
66
+
67
+ You grant to the Project and to recipients of software distributed by the Project
68
+ a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable patent
69
+ licence to make, have made, use, offer to sell, sell, import and otherwise
70
+ transfer the Work.
71
+
72
+ This licence applies only to those patent claims licensable by You that are
73
+ necessarily infringed by Your Contribution alone, or by the combination of Your
74
+ Contribution with the Work to which it was submitted.
75
+
76
+ If any entity institutes patent litigation alleging that Your Contribution, or the
77
+ Work it was submitted to, constitutes direct or contributory patent infringement,
78
+ any patent licences granted under this agreement to that entity terminate as of
79
+ the date such litigation is filed.
80
+
81
+ ### 4. Your representations
82
+
83
+ You represent that:
84
+
85
+ - **(a)** You are legally entitled to grant the above licences.
86
+ - **(b)** Each Contribution is Your original creation.
87
+ - **(c)** If Your employer has rights to intellectual property You create, You
88
+ have received permission to make the Contribution on behalf of that employer,
89
+ or Your employer has waived such rights, or Your employer has executed the
90
+ Corporate CLA below.
91
+ - **(d)** Your Contribution includes complete details of any third-party licence
92
+ or other restriction of which You are personally aware and which is associated
93
+ with any part of it.
94
+
95
+ ### 5. What you are not promising
96
+
97
+ You are not expected to provide support for Your Contributions.
98
+
99
+ **Unless required by applicable law or agreed in writing, You provide Your
100
+ Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND**,
101
+ either express or implied, including any warranty of title, non-infringement,
102
+ merchantability or fitness for a particular purpose.
103
+
104
+ ### 6. Notification
105
+
106
+ You agree to notify the Project of any facts or circumstances of which You become
107
+ aware that would make these representations inaccurate in any respect.
108
+
109
+ ### 7. Reciprocity
110
+
111
+ The Project undertakes that Your Contribution will remain available under the
112
+ AGPL-3.0, or a later version of the AGPL, in a publicly accessible repository, for
113
+ as long as the Project distributes it at all.
114
+
115
+ *This clause is not in the standard Apache ICLA. It is here because a CLA without
116
+ it asks a contributor to trust a promise that nobody wrote down: that the project
117
+ will not simply go closed once it has enough contributed code. This is that
118
+ promise, written down.*
119
+
120
+ ---
121
+
122
+ ## Corporate Contributor Licence Agreement
123
+
124
+ Where contributions are made by employees in the course of their employment, the
125
+ employer signs this instead of, or in addition to, the individual agreement.
126
+
127
+ The terms of sections 1 to 7 above apply, with "You" meaning the Corporation, plus:
128
+
129
+ ### 8. Schedule of authorised contributors
130
+
131
+ The Corporation lists below the employees authorised to submit Contributions on
132
+ its behalf, and undertakes to keep the list current by written notice.
133
+
134
+ ```
135
+ Corporation: ______________________________________
136
+ Registered address: ______________________________________
137
+ Company number: ______________________________________
138
+
139
+ Authorised contributors (name / email / repository handle):
140
+ 1. ____________________________________________________
141
+ 2. ____________________________________________________
142
+ 3. ____________________________________________________
143
+
144
+ Point of contact: ____________________________________
145
+ Signed: ____________________ Date: __________
146
+ Name and title: ____________________________________
147
+ ```
148
+
149
+ ---
150
+
151
+ ## How to sign
152
+
153
+ **Individuals** — add a `Signed-off-by:` trailer to every commit, using your real
154
+ name and an email you control:
155
+
156
+ ```
157
+ git commit -s -m "your message"
158
+ ```
159
+
160
+ Adding that trailer means:
161
+
162
+ > I certify that I have read the cra24 Individual Contributor Licence Agreement
163
+ > version 1.0, that I am entitled to grant the licences it describes, and that I
164
+ > grant them for this contribution.
165
+
166
+ The CI checks for the trailer. A first-time contributor is also asked, once, to
167
+ confirm in the pull request thread that they have read this document.
168
+
169
+ **Corporations** — send the completed schedule above to `amr.benabdessalem@gmail.com` before your
170
+ employees' first pull request.
171
+
172
+ ---
173
+
174
+ ## Records
175
+
176
+ Signed corporate agreements are retained by `Amr Ben Abdessalem (France)`. Sign-off
177
+ trailers live in the git history, which is the record for individual contributors.
178
+
179
+ Personal data in these records is processed on the basis of legitimate interest in
180
+ establishing the provenance of the code, retained for as long as the Project
181
+ distributes the Contribution, and is not used for any other purpose.
182
+ `[CONFIRM YOUR GDPR POSITION AND ADD A PRIVACY CONTACT.]`