# OpenSSF Best Practices badge: every answer, ready to paste
This page exists because the badge cannot be filled in from a script. The
BadgeApp accepts `POST` and `PUT` only from a logged-in browser session; there
is no API key. So the answers are written here, and a human pastes them.
**Why bother.** The badge is worth 5 points out of 10 on the Scorecard
`CII-Best-Practices` check, which is 2.5 % of the aggregate score. That alone
would be a poor reason. The better one is that the questionnaire asks 67
questions a maintainer should be able to answer, and answering them honestly is
how you find out which ones you cannot.
**Read the answers before pasting them.** Several are `Unmet` on purpose. An
answer that claims a practice nobody follows is exactly the fault this
repository spends its time hunting, and the badge is not worth acquiring that
way.
## Before you start
1. Sign in at with the GitHub account that owns
the repository.
2. Add the project with the repository URL:
`https://github.com/stephrobert/collection-scaleway`
3. The form autofills a dozen answers from GitHub. **Check them anyway**: the
autofill guesses from file names, and a guess that lands on the right answer
is still a guess.
4. Work down the page in the order below. It follows the page exactly.
The badge is awarded when every `MUST` is met and every `SHOULD` is met or
justified. `SUGGESTED` criteria may be left `Unmet` without justification.
Three answers are possible: **Met**, **Unmet**, **N/A**. Some criteria require a
URL alongside `Met`; they are marked `(URL required)` below.
---
## Basics
### Basic project website content
**`description_good`** · MUST · **Met**
> The project produces an Ansible collection for the Scaleway APIs, and the
> generator that writes it. The README says what it is in its first two
> sentences, and states the boundary that decides the rest: Terraform
> provisions resources, Ansible operates existing ones.
**`interact`** · MUST · **Met**
> Contribution and bug reporting are described in CONTRIBUTING.md, linked from
> the README, with issue templates that ask the question that settles a report
> before the conversation starts.
**`contribution`** · MUST · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/blob/0.7.0/CONTRIBUTING.md
> CONTRIBUTING.md describes what to run before pushing, what gets refused on
> sight, and how a decision is recorded as an override carrying its reason.
**`contribution_requirements`** · SHOULD · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/blob/0.7.0/CONTRIBUTING.md
> The same file states the requirements: no hand-edited generated file, a
> falsification mutation for every new guard, and the language boundary between
> what is published and what is not.
### FLOSS license
**`floss_license`** · MUST · **Met**
> GPL-3.0-or-later, declared in `LICENSE` and in `galaxy.yml`.
**`floss_license_osi`** · SUGGESTED · **Met**
> GPL-3.0-or-later is OSI-approved.
**`license_location`** · MUST · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/blob/0.7.0/LICENSE
### Documentation
**`documentation_basics`** · MUST · **Met**
> The collection README covers installation, authentication, a sixty-second
> first run, and the module tables. Every published module carries its own
> reference page, generated from the same model as its `argument_spec` so the
> two cannot describe different things.
**`documentation_interface`** · MUST · **Met**
> Each module publishes `DOCUMENTATION`, `EXAMPLES` and `RETURN`, rendered by
> `ansible-doc` and on Galaxy. As of 0.3.0, 297 options, 62 returned keys and
> 669 returned fields carry a description, and the release gate refuses to
> publish a version where one does not.
### Other
**`sites_https`** · MUST · **Met**
> The repository, the released archives and the Galaxy pages are served over
> HTTPS only.
**`discussion`** · MUST · **Met**
> GitHub Issues and pull requests. Searchable, addressable by URL, open to new
> participants, and requiring no proprietary client.
**`english`** · SHOULD · **Met**
> Everything published is in English: both READMEs, `docs/`, `galaxy.yml`, the
> changelog, and the documentation of every module. The code, its comments and
> the test names are in French, which is the maintainer's language and does not
> reach a user of the collection. The boundary is written down in the
> repository's own instructions.
**`maintained`** · MUST · **Met**
---
## Change Control
### Public version-controlled source repository
**`repo_public`** · MUST · **Met**
https://github.com/stephrobert/collection-scaleway
**`repo_track`** · MUST · **Met** (git)
**`repo_interim`** · MUST · **Met**
> Every change lands on `main` through a pull request; interim versions are
> public as soon as they are pushed.
**`repo_distributed`** · SUGGESTED · **Met** (git)
### Unique version numbering
**`version_unique`** · MUST · **Met**
> Each release carries a unique version in `galaxy.yml`, and the release gate
> refuses to publish when the tag and that file disagree.
**`version_semver`** · SUGGESTED · **Met**
> Semantic versioning, which Ansible requires of collections. The next number is
> derived from the changelog fragments rather than chosen.
**`version_tags`** · SUGGESTED · **Met**
> Every release is a signed tag, and the workflow publishes on a tag and nothing
> else.
### Release notes
**`release_notes`** · MUST · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/blob/0.7.0/ansible_collections/stephrobert/scaleway/CHANGELOG.rst
> Composed by `antsibull-changelog` from fragments written with the change they
> describe. The release gate refuses to publish while a fragment is still
> pending, so a change cannot ship undescribed.
**`release_notes_vulns`** · MUST · **Met**
> No publicly known vulnerability has been fixed to date. The changelog
> mechanism carries a `security_fixes` section for the day one is, and the
> release notes are composed from those fragments rather than written by hand.
---
## Reporting
### Bug-reporting process
**`report_process`** · MUST · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/blob/0.7.0/CONTRIBUTING.md#issues
**`report_tracker`** · SHOULD · **Met**
https://github.com/stephrobert/collection-scaleway/issues
**`report_responses`** · MUST · **Met**
> The repository has a single maintainer, and reports are answered. The issue
> templates ask for what settles most reports in one exchange: for a module
> that fails, what Ansible printed; for a module that is missing, what
> `mise run report` says about the operation, since every operation is either
> generated, excluded with its reason, or unclassified and failing CI.
**`enhancement_responses`** · SHOULD · **Met**
> Enhancement requests are answered, including when the answer is no. The
> project has a written boundary that refuses a whole class of them: Terraform
> provisions resources, Ansible operates existing ones, so an operation that
> creates or deletes resources is out of scope even when the generator could
> produce it.
**`report_archive`** · MUST · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/issues?q=is%3Aissue
### Vulnerability report process
**`vulnerability_report_process`** · MUST · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/blob/0.7.0/SECURITY.md
**`vulnerability_report_private`** · MUST · **Met** (URL required)
https://github.com/stephrobert/collection-scaleway/security/advisories/new
**`vulnerability_report_response`** · MUST · **Met**
> SECURITY.md commits to an acknowledgement within 3 working days, a first
> assessment with its severity and reasoning within 10, and a fix or a reasoned
> decision not to fix within 90. Those deadlines are written for what one
> person can hold rather than copied from a template.
---
## Quality
### Working build system
**`build`** · MUST · **Met**
> `mise run package` builds the collection archive, checks its contents,
> installs it into a throwaway directory and asks `ansible-doc` for its
> documentation. A file present in an archive is not a module Ansible can load,
> which is why the last two steps exist.
**`build_common_tools`** · SUGGESTED · **Met** (mise, uv, pip, `ansible-galaxy`)
**`build_floss_tools`** · SHOULD · **Met**
> Every tool in the build is FLOSS.
### Automated test suite
**`test`** · MUST · **Met**
> 1154 unit tests, plus `ansible-test sanity` on four `ansible-core` versions and
> an integration run of the shipped playbooks against a local emulator.
**`test_invocation`** · SHOULD · **Met**
> `mise run test` for the unit suite, `mise run check` for everything a pull
> request must pass.
**`test_most`** · SUGGESTED · **Unmet**
> No line-coverage figure is measured or published, so claiming "most" would be
> a claim nobody checked. What the repository measures instead is whether each
> guard actually bites: `/falsify` neutralises every declared guard one at a
> time in a copy outside the repository and requires the named test to fail.
> That is a different property from coverage, and it is not a substitute for it.
**`test_continuous_integration`** · SUGGESTED · **Met**
> Every pull request runs `mise run check`, `ansible-test sanity` on four
> `ansible-core` versions, the archive build, the integration run and the
> example platform.
### New functionality testing
**`test_policy`** · MUST · **Met**
> CONTRIBUTING.md requires a test with new functionality, and requires a
> falsification mutation with any new guard, because a guard whose removal
> leaves the tests green is a comment.
**`tests_are_added`** · MUST · **Met**
**`tests_documented_added`** · SUGGESTED · **Met**
> The requirement is in CONTRIBUTING.md and repeated as a checklist item in the
> pull request template, with the two ways of writing a mutation that proves
> nothing, both of which happened here.
### Warning flags
**`warnings`** · MUST · **Met**
> `ruff check`, `ruff format --check`, and `mypy` in strict mode.
**`warnings_fixed`** · MUST · **Met**
> All three are gates: a warning fails the build rather than being reported.
**`warnings_strict`** · SUGGESTED · **Met**
> `mypy` runs in strict mode, and the generator refuses an untranslatable type
> rather than defaulting it to `str`.
---
## Security
### Secure development knowledge
**`know_secure_design`** · MUST · **Met**
> The maintainer understands the principles. Where they apply here, they are
> applied and written down: least privilege on every CI job, no secret reachable
> from a `pull_request` run, a publication token confined to an environment that
> only version tags may deploy to, and a runtime that centralises error handling
> so that no code path copies a secret into a log.
**`know_common_errors`** · MUST · **Met**
> The class that matters for this project is secret leakage through generated
> code. The generator marks a sensitive parameter `no_log` from a deliberately
> wide name heuristic, on the stated reasoning that a false positive is fixed by
> an override while a false negative writes a secret into Ansible's log. A
> falsification mutation proves that guard bites.
### Use basic good cryptographic practices
**`crypto_published`** · MUST · **Met**
> The collection talks to the Scaleway API over TLS through the official SDK. It
> implements no cryptography of its own.
**`crypto_call`** · SHOULD · **Met**
> No cryptography is implemented in this project.
**`crypto_floss`** · MUST · **Met**
> Python's standard library and OpenSSL, through the SDK.
**`crypto_keylength`** · MUST · **N/A**
> The project generates no keys and specifies no key lengths. Release signing is
> keyless: the signature binds the archive to the identity of the workflow that
> produced it, and the key material is Sigstore's.
**`crypto_working`** · MUST · **Met**
> No broken or obsolete algorithm is used or depended upon.
**`crypto_weaknesses`** · SHOULD · **Met**
**`crypto_pfs`** · SHOULD · **N/A**
> The project operates no server and terminates no TLS connection.
**`crypto_password_storage`** · MUST · **N/A**
> The project stores no password and authenticates no user. Credentials are read
> from the environment or the Scaleway configuration file and passed to the SDK.
**`crypto_random`** · MUST · **N/A**
> No random value is generated for a security purpose. The generator is
> deliberately deterministic: the same contract produces the same files, byte
> for byte, and a golden test measures it.
### Secured delivery against man-in-the-middle attacks
**`delivery_mitm`** · MUST · **Met**
> The repository is cloned over HTTPS or SSH, the archive is downloaded from
> Galaxy over HTTPS, and each release carries a signed tag, a keyless signature
> over the archive and a build provenance attestation. SECURITY.md publishes the
> two commands that verify them, and the release workflow runs the first one on
> the archive before publishing it.
**`delivery_unsigned`** · MUST · **Met**
### Publicly known vulnerabilities fixed
**`vulnerabilities_fixed_60_days`** · MUST · **Met**
> None is known. OSV-Scanner runs on every pull request and weekly, Dependabot
> watches pip and GitHub Actions, and the dependency lock carries hashes.
**`vulnerabilities_critical_fixed`** · SHOULD · **Met**
### Other security issues
**`no_leaked_credentials`** · MUST · **Met**
> TruffleHog runs on every pull request. The publication token lives in a GitHub
> environment reachable only from version tags, and is passed through the
> environment rather than on a command line, because a command line ends up in
> a log and a log gets shared.
---
## Analysis
### Static code analysis
**`static_analysis`** · MUST · **Met**
> CodeQL on Python, `ruff` and `mypy --strict` as gates, and four workflow
> scanners: actionlint, zizmor, poutine and plumber.
**`static_analysis_common_vulnerabilities`** · SUGGESTED · **Met**
> CodeQL's security query suite.
**`static_analysis_fixed`** · MUST · **Met**
> All of them are gates rather than reports.
**`static_analysis_often`** · SUGGESTED · **Met**
> On every pull request, and weekly for the ones that need a trusted context.
### Dynamic code analysis
**`dynamic_analysis`** · SUGGESTED · **Met**
> The test suite runs on every change; the shipped playbooks are played against
> a local emulator on every pull request; and the example platform is played
> against the real Scaleway account before a release, with a differential check
> that the account returns to the resources it held before.
**`dynamic_analysis_unsafe`** · SUGGESTED · **N/A**
> Python is memory-safe. There is no C or C++ in this project.
**`dynamic_analysis_enable_assertions`** · SUGGESTED · **Met**
> Assertions are enabled: the code is never run with `-O`.
**`dynamic_analysis_fixed`** · MUST · **Met**
---
## After you submit
The badge percentage appears immediately; Scorecard picks it up on its next
weekly run, so `CII-Best-Practices` will still read 0 for a few days. That delay
is the tool's, not the project's.
Add the badge to the root README when the percentage reaches 100 %, next to the
others:
```markdown
[](https://www.bestpractices.dev/projects/)
```
`` is the number in the project URL after registration. Tell the maintainer
of this file what it is, and it will be added to the derived block rather than
pasted by hand.