Skip to content

Commit 6cbc8ad

Browse files
committed
Scanner: the licence, the files a public repo needs, and the holes publishing invites
Splitting the scanner out gave it a repository. It did not make it publishable. Six of the eight prerequisites written down in the feasibility pass were still missing, and two of the things a reviewer looks for first were real. **Licence.** `pyproject.toml` claimed MIT and no LICENSE existed, so neither the wheel nor the repository carried any terms at all. It is now **AGPL-3.0-or-later** with the canonical text, declared as PEP 639 metadata — the built wheel reports `License-Expression: AGPL-3.0-or-later` and ships LICENSE and NOTICE in `dist-info/licenses`. This is the recommendation from the analysis, not a decision anyone signed off: the network clause is what stops a competitor running a rival service off this engine without publishing their changes, and a commercial exception is what keeps that from costing revenue. Switching to BSL or back to MIT is three lines. **NOTICE.** `data/jurisdiction_places.json` is built from GeoNames dumps, which are CC BY 4.0 and require attribution and a statement that the data was modified. Neither existed. Shipping it under a bare MIT claim misdescribed the artifact; shipping it publicly would have been an attribution failure. **CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, CLA.** CONTRIBUTING says the thing that is not visible from outside — that this repository is published from a private workspace, so a merged change returns with a different hash. The CLA is a licence grant with a sublicense right, not an assignment: contributors keep their copyright, and the project keeps the ability to sell the commercial exception. It is a draft and says so. **The gazetteer script wrote outside the repository.** `parents[2]` resolved to the workspace root, which in the split repository is its *parent*: a rebuild would have written `../scanner/src/...`. Re-anchored on the project root, which is the same directory in both layouts. Workspace-Commit: 62fb4a61008b588cabcd305166716f6a808e5a76 --- Then the security work, because publishing invites it and because two of these were genuinely wrong. Each was reproduced before it was fixed. **Redirects left the public internet.** `is_public_url` guarded the entry URL, then `follow_redirects=True` handed the chain to httpx and only the *landing* URL was re-checked. That discards the body but does not prevent the request: against a local pair of servers, a public entry URL redirecting to 127.0.0.1 reached the private endpoint every time, and the response was read in full. A repository declaring such a homepage could have the scanner probe the network it runs in and read the answers off the timings. `icon.public_stream` now walks the chain by hand and vets every hop before requesting it; `llms_txt` uses it too. **The Go vanity lookup was not guarded at all.** `go_module_repository_url` fetches `https://{module}?go-get=1`, where `module` comes from the scanned repository's own `go.mod` — the one fetch in that module whose host the audited project picks. It had no host check and no size limit. That HTTPS-only happened to block the plain-HTTP metadata endpoints is luck, not a control. It now goes through the same guard and a 512 kB cap. **Manifests were read unbounded.** Every other fetch in `ecosystems` addresses a registry; manifests come from raw.githubusercontent, where GitHub serves single files up to 100 MB, a scan reads up to twenty of them, and every text is retained for classification. Committing a padded `package.json` was a way to take a worker's memory. `_get_manifest_text` streams against a 2 MB cap and keeps `_get`'s retry shape, because dropping a manifest on a 429 would file "slow down" under "no package here". **XML entity expansion.** Measured rather than assumed: on CPython 3.13 the stdlib parser already refuses external entities — no file disclosure, no request smuggled through a DOCTYPE — but *internal* expansion works, and four nesting levels turn under a kilobyte into 30,000 characters. `xmlsafe` refuses entity declarations outright; nothing here has a legitimate reason to declare one. **The test doubles were the reason none of this was caught.** `test_icon`, `test_llms_txt` and `test_ecosystems` used hand-written clients with only a `.stream()` method, which cannot express a redirect at all — so the redirect handling was unexercised for as long as it was wrong. They are real `httpx.Client`s over `MockTransport` now, and six tests assert on what reached the transport, which is the only assertion that means anything for a guard whose job is to stop a request being made. 1102 tests pass. METRICS_VERSION, SCHEMA_VERSION and the calibration snapshot are untouched: no score moves and no rescore is triggered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 6a4ebef commit 6cbc8ad

19 files changed

Lines changed: 1490 additions & 129 deletions

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ uv.lock
2323
!src/scanner/data/platform_icon_hashes.json
2424
*.html
2525
storage/
26+
# Downloaded GeoNames dumps for scripts/build_jurisdiction_gazetteer.py
27+
.gazetteer-cache/

‎CLA.md‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Contributor Licence Agreement
2+
3+
**This is a draft. It has not been reviewed by a lawyer. Read it, and if you
4+
are contributing on behalf of an employer, have them read it too.**
5+
6+
## Why this exists
7+
8+
inspect-scanner is released under AGPL-3.0-or-later. Some users cannot accept
9+
AGPL — its network clause reaches their hosted service — and they buy a
10+
commercial licence instead. That revenue is what pays for the project.
11+
12+
Selling such a licence requires the right to license *every line* in the
13+
release. Contributions do not grant that by default: you keep the copyright in
14+
your patch, and AGPL alone would not let anyone, including us, ship it under
15+
other terms. This agreement supplies the missing permission — and nothing else.
16+
17+
**It is a licence grant, not an assignment.** You keep the copyright in your
18+
work. You may use, publish and relicense your own contribution however you like,
19+
to anyone, for ever. Signing takes nothing away from you.
20+
21+
## Agreement
22+
23+
By signing, You agree to the following for all past and future Contributions
24+
You submit to this project.
25+
26+
**1. Definitions.** "You" is the copyright owner, or the legal entity authorised
27+
by the copyright owner, entering into this agreement. "Contribution" is any
28+
work of authorship You intentionally submit to the project — code, tests,
29+
documentation, data — through a pull request, issue, patch or any other
30+
channel, excluding anything You conspicuously mark as "Not a Contribution".
31+
"Project Owner" is Vitalii Stepanenko, the maintainer of inspect-scanner.
32+
33+
**2. Copyright licence.** You grant the Project Owner a perpetual, worldwide,
34+
non-exclusive, royalty-free, irrevocable copyright licence — **with the right
35+
to sublicense through multiple tiers** — to reproduce, prepare derivative works
36+
of, publicly display, publicly perform, and distribute Your Contribution and
37+
such derivative works, **under any licence terms, including proprietary terms**.
38+
39+
**3. Patent licence.** You grant the Project Owner a perpetual, worldwide,
40+
non-exclusive, royalty-free, irrevocable patent licence, with the right to
41+
sublicense, to make, have made, use, offer to sell, sell, import and otherwise
42+
transfer Your Contribution and the work it is combined with, covering only those
43+
claims You own or control that are necessarily infringed by Your Contribution
44+
alone or by its combination with the project. If any entity brings patent
45+
litigation alleging that the project or a Contribution infringes a patent, the
46+
patent licences granted here to that entity terminate.
47+
48+
**4. You keep your rights.** You retain all right, title and interest in Your
49+
Contribution. Nothing here transfers ownership, and nothing here limits what You
50+
may do with Your own work elsewhere.
51+
52+
**5. Your representations.** You represent that:
53+
54+
a. Each Contribution is Your original creation, or You otherwise have the
55+
right to grant the licences above;
56+
b. If Your employer has rights in work You create — through an employment
57+
agreement, or because the work was made in the course of employment — You
58+
have permission to make the Contribution on their behalf, or they have
59+
waived those rights, or they have signed this agreement themselves;
60+
c. Your Contribution does not knowingly infringe anyone's copyright, patent,
61+
trademark or trade secret;
62+
d. Any third-party material in Your Contribution is identified as such, with
63+
its licence, in the Contribution itself.
64+
65+
**6. No obligation and no warranty.** The Project Owner is not required to use
66+
or merge any Contribution. Except for the representations in section 5, You
67+
provide Your Contribution "AS IS", without warranties or conditions of any kind.
68+
69+
**7. Notice.** You agree to tell the Project Owner if any of the
70+
representations in section 5 stops being true.
71+
72+
## How to sign
73+
74+
Comment on your pull request with exactly:
75+
76+
```
77+
I have read the CLA Document and I hereby sign the CLA.
78+
```
79+
80+
Once per person, not per pull request. If you are signing for a company rather
81+
than for yourself, say so in the same comment and name the company, and we will
82+
follow up.
83+
84+
If any part of this stops you from contributing, open an issue and say which
85+
part. A contribution lost to an unread paragraph helps nobody.

‎CODE_OF_CONDUCT.md‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
2+
# Contributor Covenant Code of Conduct
3+
4+
## Our Pledge
5+
6+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
7+
8+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
9+
10+
## Our Standards
11+
12+
Examples of behavior that contributes to a positive environment for our community include:
13+
14+
* Demonstrating empathy and kindness toward other people
15+
* Being respectful of differing opinions, viewpoints, and experiences
16+
* Giving and gracefully accepting constructive feedback
17+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
18+
* Focusing on what is best not just for us as individuals, but for the overall community
19+
20+
Examples of unacceptable behavior include:
21+
22+
* The use of sexualized language or imagery, and sexual attention or advances of any kind
23+
* Trolling, insulting or derogatory comments, and personal or political attacks
24+
* Public or private harassment
25+
* Publishing others' private information, such as a physical or email address, without their explicit permission
26+
* Other conduct which could reasonably be considered inappropriate in a professional setting
27+
28+
## Enforcement Responsibilities
29+
30+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
31+
32+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
33+
34+
## Scope
35+
36+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
37+
38+
## Enforcement
39+
40+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at mail@inspect.software. All complaints will be reviewed and investigated promptly and fairly.
41+
42+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
43+
44+
## Enforcement Guidelines
45+
46+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
47+
48+
### 1. Correction
49+
50+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
51+
52+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
53+
54+
### 2. Warning
55+
56+
**Community Impact**: A violation through a single incident or series of actions.
57+
58+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
59+
60+
### 3. Temporary Ban
61+
62+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
63+
64+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
65+
66+
### 4. Permanent Ban
67+
68+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
69+
70+
**Consequence**: A permanent ban from any sort of public interaction within the community.
71+
72+
## Attribution
73+
74+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
75+
76+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
77+
78+
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
79+
80+
[homepage]: https://www.contributor-covenant.org
81+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
82+
[Mozilla CoC]: https://github.com/mozilla/diversity
83+
[FAQ]: https://www.contributor-covenant.org/faq
84+
[translations]: https://www.contributor-covenant.org/translations
85+

‎CONTRIBUTING.md‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Contributing
2+
3+
Thank you for wanting to improve the engine behind the public record.
4+
5+
## First, something unusual about this repository
6+
7+
This repository is **published from a private workspace**, not developed in.
8+
`main` here is produced by replaying every commit that touched the scanner in
9+
that workspace — each one carries a `Workspace-Commit:` trailer naming where it
10+
came from.
11+
12+
Two consequences for you:
13+
14+
- **Pull requests are welcome and are read.** What happens on merge is that a
15+
maintainer applies the change upstream and it returns here on the next
16+
publish, so your commit arrives with a different hash. Your authorship is
17+
preserved; the hash is not. We will say so on the PR rather than leave you
18+
wondering why it closed without a merge commit.
19+
- **Do not force-push or rewrite `main`.** The trailer on its tip is the only
20+
thing that tells the publisher where to resume.
21+
22+
If that model rules out the contribution you had in mind, open an issue first
23+
and say so — it is a deliberate choice, but not a permanent one.
24+
25+
## Setting up
26+
27+
```bash
28+
uv sync --extra dev
29+
uv run pytest
30+
```
31+
32+
The suite runs in about ten seconds and touches no network: every HTTP
33+
interaction in the tests is stubbed. If a change you make needs a live request
34+
to be tested, that is a sign the seam is in the wrong place.
35+
36+
Three test modules — `test_methodology_version_sync`, `test_detail_translations`
37+
and `test_content_translations` — compare the scanner against the website that
38+
consumes it, and **skip here** because the website is not in this repository.
39+
They are not dead: they run upstream, and they are the gate on anything that
40+
crosses that boundary. Green here does not mean green there.
41+
42+
## What a good change looks like
43+
44+
- **Comments say why, not what.** The codebase is dense with the reasoning
45+
behind decisions — which alternative was tried, which data made it wrong.
46+
Match that. A comment restating the line below it will be asked about.
47+
- **Tests come with behaviour.** Every scoring rule, parser and heuristic here
48+
has tests naming the case it exists for. A bug fix should carry the input that
49+
used to break it.
50+
- **Missing data is never a zero.** When a signal cannot be observed, exclude it
51+
and renormalize the remaining weights. Scoring an unknown as zero
52+
misrepresents the project, and the whole methodology rests on not doing it.
53+
- **Signals, not warranties.** Nothing here is a code audit or a security
54+
guarantee, and no message, note or metric name should imply that it is.
55+
56+
## Changes that need a maintainer
57+
58+
Some things cannot be completed from this repository alone. Propose them, but
59+
expect a maintainer to finish them:
60+
61+
- **`METRICS_VERSION` (`src/scanner/metrics.py`).** Bumping it rescores the
62+
entire public record and requires the website's own constant to move in the
63+
same release. Change scoring behaviour in your PR if you have a case for it;
64+
leave the version to us.
65+
- **`SCHEMA_VERSION` (`src/scanner/models.py`)** and the calibration table in
66+
`src/scanner/calibration.py`, for the same reason — stored reports are
67+
replayed through both.
68+
- **New ecosystems.** The registry side is straightforward; the catalogue
69+
ingestion and eligibility policy that has to accompany it live upstream.
70+
71+
## Contributor Licence Agreement
72+
73+
The project is AGPL-3.0-or-later, and commercial licences are sold to fund it.
74+
Selling one requires the right to license every line in the release, which a
75+
contribution does not grant by default. So a first pull request needs
76+
[CLA.md](CLA.md) signed — once, then never again.
77+
78+
It is a licence grant, not a copyright assignment: **you keep the copyright in
79+
your work.** Read it before signing; if something in it stops you, say so on the
80+
issue and we will talk rather than lose the contribution.
81+
82+
## Reporting bugs and vulnerabilities
83+
84+
Ordinary bugs: open an issue with the repository you scanned, the command, and
85+
what you expected. A scan is reproducible from its URL, which makes almost every
86+
report actionable.
87+
88+
Security issues: **not** in a public issue — see [SECURITY.md](SECURITY.md).

0 commit comments

Comments
 (0)