|
| 1 | +# Proposal: OpenSSF Best Practices — Silver badge, Scorecard fixes, and the road to Gold |
| 2 | + |
| 3 | +- **Status:** Proposed (decisions on review policy, coverage pacing, fuzzing scope, |
| 4 | + security-review route, and timeline recorded 2026-07-29 — see §Decisions) |
| 5 | +- **Date:** 2026-07-29 |
| 6 | +- **Scope:** [OpenSSF Best Practices badge](https://www.bestpractices.dev/projects/12651) (Silver/Gold levels), [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/dns-aid/dns-aid-core), and the OSPS Security Baseline questionnaire |
| 7 | + |
| 8 | +## Summary |
| 9 | + |
| 10 | +DNS-AID earned the **Passing** badge (100%) on 2026-04-25 and has not touched the |
| 11 | +badge entry since. The Silver questionnaire sits at 15% and Gold at 22% — almost |
| 12 | +entirely because the questions are **unanswered**, not because the practices are |
| 13 | +missing. An audit of the badge API against the current repository shows that the |
| 14 | +large majority of Silver criteria are already satisfied by existing files, CI |
| 15 | +workflows, and release automation; a handful need small, well-scoped changes. |
| 16 | + |
| 17 | +Separately, the badge entry still points at the pre-rename |
| 18 | +`github.com/infobloxopen/dns-aid-core` URL, which is why OpenSSF Scorecard's |
| 19 | +`CII-Best-Practices` check scores **0/10** ("no effort to earn a badge detected") |
| 20 | +despite the achieved badge. Fixing that one URL raises the overall Scorecard |
| 21 | +score at zero engineering cost. |
| 22 | + |
| 23 | +This proposal sequences the work into four phases: an immediate metadata fix, |
| 24 | +documentation PRs, CI/process hardening, and a Gold execution plan targeting |
| 25 | +**Gold-ready within 1–2 release cycles**, with `contributors_unassociated` as |
| 26 | +the sole criterion gated on ecosystem growth. |
| 27 | + |
| 28 | +## Decisions (2026-07-29) |
| 29 | + |
| 30 | +Recorded from maintainer review of the draft; facts verified against the GitHub |
| 31 | +API the same day. |
| 32 | + |
| 33 | +| Topic | Decision / fact | |
| 34 | +| --- | --- | |
| 35 | +| Badge entry access | Held by @iracic82 only; questionnaire edits are coordinated with him (or he adds a co-owner login on the entry) | |
| 36 | +| Org admin | @ivanglabbeek is a dns-aid org owner — settings changes are direct actions, not requests | |
| 37 | +| Org 2FA status | **0 members have 2FA disabled** (verified via API) — `require_2FA` can be enforced with no member ejections | |
| 38 | +| Branch protection on `main` | Already requires 1 approving review with stale-review dismissal + 8 required status checks (strict). Gaps: `enforce_admins` off, last-push approval off | |
| 39 | +| Review policy | 1 approval on **all** PRs (already configured); add `enforce_admins` so it binds admins too | |
| 40 | +| Coverage pacing | **Dedicated push now** to 90% statement / 80% branch, not a slow ratchet | |
| 41 | +| Fuzzing & builds | All three: reproducible-build CI job, Atheris harness in CI, OSS-Fuzz application | |
| 42 | +| Security review | Infoblox internal security team (reviewers independent of the dev team), written report | |
| 43 | +| External contributors | Candidates in pipeline (IETF/ecosystem); tracked as the one open Gold blocker | |
| 44 | +| Timeline | Gold-ready in the next 1–2 release cycles | |
| 45 | + |
| 46 | +## Current state |
| 47 | + |
| 48 | +| Metric | Value | Source | |
| 49 | +| --- | --- | --- | |
| 50 | +| Best Practices — Passing | **100% (achieved 2026-04-25)** | bestpractices.dev project 12651 | |
| 51 | +| Best Practices — Silver | 15% (criteria unanswered) | same | |
| 52 | +| Best Practices — Gold | 22% (criteria unanswered) | same | |
| 53 | +| OSPS Security Baseline (tiered) | unanswered | same | |
| 54 | +| Scorecard (aggregate) | 8.0 / 10 | api.securityscorecards.dev | |
| 55 | +| Unit-test coverage | 79% combined statement+branch; ≈81% statement-only (measured 2026-07-29, 2142 tests) | local run of `tests/unit/` | |
| 56 | + |
| 57 | +Scorecard checks below 10: `CII-Best-Practices` 0, `Fuzzing` 0, `Code-Review` 2, |
| 58 | +`Branch-Protection` 4, `Pinned-Dependencies` 8, `Signed-Releases` 8, |
| 59 | +`Token-Permissions` 9. |
| 60 | + |
| 61 | +## Phase 0 — Immediate metadata fix (no code) |
| 62 | + |
| 63 | +1. **Update the badge entry URLs.** The bestpractices.dev entry's `repo_url` and |
| 64 | + `homepage_url` still read `https://github.com/infobloxopen/dns-aid-core`. |
| 65 | + Update both to `https://github.com/dns-aid/dns-aid-core` (badge-entry owner |
| 66 | + action; bestpractices.dev supports repo URL changes with a rename note). |
| 67 | + This alone should move Scorecard `CII-Best-Practices` from 0 to 5 (passing |
| 68 | + badge detected), and to 7 once Silver is achieved. |
| 69 | + |
| 70 | +## Phase 1 — Silver: answer what is already true |
| 71 | + |
| 72 | +These criteria are already satisfied; the work is filling in the questionnaire |
| 73 | +with evidence links. No repository changes required. |
| 74 | + |
| 75 | +| Criterion | Evidence already in the repo | |
| 76 | +| --- | --- | |
| 77 | +| `dco` | [DCO](../../DCO) file; `dco.yml` workflow enforces sign-off on every PR | |
| 78 | +| `governance` | [GOVERNANCE.md](../../GOVERNANCE.md) (roles, lazy consensus, voting) | |
| 79 | +| `code_of_conduct` | [CODE_OF_CONDUCT.md](../../CODE_OF_CONDUCT.md) | |
| 80 | +| `roles_responsibilities` | GOVERNANCE.md §Roles; [MAINTAINERS.md](../../MAINTAINERS.md) role table | |
| 81 | +| `bus_factor` | Three maintainers listed in MAINTAINERS.md (≥2 required) | |
| 82 | +| `documentation_architecture` | [docs/architecture.md](../architecture.md) | |
| 83 | +| `documentation_quick_start` | [docs/getting-started.md](../getting-started.md) | |
| 84 | +| `documentation_current` | Docs updated in lockstep with releases (CHANGELOG discipline) | |
| 85 | +| `documentation_achievements` | Badge row in README | |
| 86 | +| `coding_standards` / `coding_standards_enforced` | CONTRIBUTING.md; ruff + mypy jobs are required CI | |
| 87 | +| `maintenance_or_update` | SECURITY.md supported-versions table; SUPPORT.md | |
| 88 | +| `vulnerability_response_process` | SECURITY.md response timeline (48h / 7d / 30d) | |
| 89 | +| `external_dependencies` | pyproject.toml + uv.lock enumerate all dependencies | |
| 90 | +| `dependency_monitoring` | Dependabot + nightly `pip-audit` in security.yml | |
| 91 | +| `updateable_reused_components` | All deps from PyPI, floor-pinned, lockfile-managed | |
| 92 | +| `interfaces_current` | [docs/api-reference.md](../api-reference.md) | |
| 93 | +| `automated_integration_testing` | `integration` job in ci.yml (mock integration suite) | |
| 94 | +| `regression_tests_added50` | Bug-fix PRs ship regression tests (see recent history, e.g. #201) | |
| 95 | +| `test_policy_mandated` | CONTRIBUTING.md "Tests (required)" checklist | |
| 96 | +| `installation_common` / `installation_development_quick` | `pip install dns-aid`; CONTRIBUTING dev setup | |
| 97 | +| `signed_releases` | Sigstore/cosign signatures (`.sig`/`.pem`) on every release artifact + SBOM | |
| 98 | +| `implement_secure_design` / `input_validation` | SECURITY.md (SSRF protections, input validation rules, cap-sha256 integrity) | |
| 99 | +| `crypto_*` (agility, certificate verification, TLS ≥1.2, network crypto) | httpx/TLS defaults; DANE/TLSA and DNSSEC handling documented in SECURITY.md | |
| 100 | +| `hardening` | SSRF allowlist-deny-by-default, HTTPS-only fetches, localhost-bound MCP transport | |
| 101 | +| `build_*` (repeatable, non-recursive, standard variables) | hatchling + uv.lock; pure-Python build (several N/A with justification) | |
| 102 | +| `accessibility_best_practices`, `internationalization`, `sites_password_security` | N/A with justification (library/CLI; no UI, no password-accepting site) | |
| 103 | + |
| 104 | +## Phase 2 — Silver: small, concrete repo changes |
| 105 | + |
| 106 | +Each item below is a small PR; together they close every remaining Silver MUST. |
| 107 | + |
| 108 | +1. **`documentation_roadmap` — add `docs/roadmap.md`.** The only Silver MUST with |
| 109 | + no existing artifact. One page: near-term (IETF draft tracking, backend |
| 110 | + parity), mid-term (LF onboarding goals from MAINTAINERS.md), and a pointer to |
| 111 | + the issue tracker as the source of truth. Link it from README. |
| 112 | + |
| 113 | +2. **`vulnerability_report_credit` — one paragraph in SECURITY.md** committing to |
| 114 | + credit reporters in release notes/advisories unless they request anonymity |
| 115 | + (the practice already happens; it just isn't written down). |
| 116 | + |
| 117 | +3. **`assurance_case` — add `docs/security/assurance-case.md`.** A structured |
| 118 | + argument mapping threats → mitigations. Most content already exists in |
| 119 | + SECURITY.md (SSRF, DNSSEC/AD-flag trust model, DANE modes, cap-sha256 |
| 120 | + integrity, input validation) and docs/rfc/security-considerations.md; this |
| 121 | + document arranges it as claim → argument → evidence, adds a trust-boundary |
| 122 | + diagram, and states what is explicitly out of scope (resolver compromise). |
| 123 | + |
| 124 | +4. **`test_statement_coverage80` — enforce coverage in CI.** Coverage is already |
| 125 | + measured but not gated, and the criterion is already met on the measure it |
| 126 | + uses: statement-only coverage is ≈81% (the 79% figure in CI reports includes |
| 127 | + branch coverage, which is stricter). Add `--cov-fail-under=79` to the ci.yml |
| 128 | + coverage step immediately so the achieved level cannot regress while the |
| 129 | + Gold coverage push (G3 below) is underway; the gate rises with the push and |
| 130 | + is never lowered without a recorded decision. |
| 131 | + |
| 132 | +5. **`version_tags_signed` — sign release tags.** Release *artifacts* are already |
| 133 | + Sigstore-signed; the git tags themselves are not. Update RELEASE.md to require |
| 134 | + `git tag -s` (maintainer GPG/SSH key) or gitsign for `v*` tags. |
| 135 | + |
| 136 | +6. **`access_continuity` — one paragraph in GOVERNANCE.md** documenting that at |
| 137 | + least two people hold org-owner/admin access and that the Project Lead role |
| 138 | + has a succession process (currently only implied by the election clause). |
| 139 | + |
| 140 | +## Phase 3 — Scorecard hardening (parallel to Phase 2) |
| 141 | + |
| 142 | +These raise the Scorecard score and simultaneously pre-answer Gold and OSPS |
| 143 | +Baseline questions: |
| 144 | + |
| 145 | +1. **Branch protection (currently 4/10) + Code-Review (currently 2/10).** |
| 146 | + Verified 2026-07-29: `main` already requires 1 approving review with |
| 147 | + stale-review dismissal and 8 strict status checks. The remaining gaps are |
| 148 | + `enforce_admins` (off — admins can currently merge without review, which is |
| 149 | + also why the Code-Review score is low: 5/21 recent changesets carried |
| 150 | + approvals) and optionally require-last-push-approval. Action: enable |
| 151 | + `enforce_admins`; the Code-Review score then recovers on its own as the |
| 152 | + trailing 30-changeset window fills with reviewed merges. |
| 153 | +2. **Token-Permissions (9/10).** Audit workflows for job-level `permissions` |
| 154 | + blocks (likely one workflow missing an explicit top-level `permissions: |
| 155 | + contents: read`). |
| 156 | +3. **Fuzzing (0/10).** Two tracks (decided): a small |
| 157 | + [Atheris](https://github.com/google/atheris) harness fuzzing the SVCB |
| 158 | + wire-format parser and record deserializers (the highest-value |
| 159 | + untrusted-input surface) on a weekly CI schedule, **and** an OSS-Fuzz |
| 160 | + application once the harness runs clean for a couple of weeks (needs a |
| 161 | + maintainer contact email and an `oss-fuzz` project directory PR). |
| 162 | +4. **Pinned-Dependencies (8/10).** Workflows already pin actions by SHA; the |
| 163 | + residual findings are `pip install` steps in release.yml — pin |
| 164 | + `build`/`cyclonedx-bom` versions with hashes. |
| 165 | + |
| 166 | +## Phase 4 — Gold execution plan |
| 167 | + |
| 168 | +Target: **Gold-ready within 1–2 release cycles**, meaning every Gold criterion |
| 169 | +is Met except `contributors_unassociated`, which is tracked as the single open |
| 170 | +blocker and worked via the contributor pipeline. |
| 171 | + |
| 172 | +Already met — answer on the form with evidence, no work: |
| 173 | + |
| 174 | +- `copyright_per_file` / `license_per_file` — all 86 source files carry SPDX + |
| 175 | + copyright headers. |
| 176 | +- `repo_distributed`, `test_invocation`, `test_continuous_integration` — carried |
| 177 | + over from Passing. |
| 178 | +- `crypto_used_network` / `crypto_tls12` / `hardened_site` / `hardening` — |
| 179 | + HTTPS-only fetches (TLS ≥1.2 via httpx defaults), GitHub-hosted site with |
| 180 | + HSTS, SSRF/input-validation hardening per SECURITY.md. |
| 181 | +- `dynamic_analysis` — already answered Met at Passing. |
| 182 | + |
| 183 | +Work items: |
| 184 | + |
| 185 | +- **G1 — Org settings (owner: @ivanglabbeek, immediate).** Enable "Require |
| 186 | + two-factor authentication" on the dns-aid org (`require_2FA`; verified safe — |
| 187 | + 0 members lack 2FA, so nobody gets ejected). GitHub requires TOTP/security |
| 188 | + keys rather than SMS-only for org enforcement, covering `secure_2FA`. Enable |
| 189 | + `enforce_admins` on main's branch protection (see Phase 3.1). |
| 190 | +- **G2 — Review standards (1 small PR).** Add a "Code review" section to |
| 191 | + CONTRIBUTING.md: what reviewers check (correctness, tests, security-sensitive |
| 192 | + paths, DCO), who may approve, and the rule that no change merges without a |
| 193 | + non-author approval (`code_review_standards`). `two_person_review` (≥50% of |
| 194 | + changes reviewed by a non-author) is then enforced mechanically by G1 + |
| 195 | + existing branch protection; the criterion evaluates recent history, so it |
| 196 | + becomes claimable roughly one release cycle after enforcement. |
| 197 | +- **G3 — Coverage push (the main engineering item, this cycle).** Dedicated |
| 198 | + test-writing effort to reach 90% statement / 80% branch (currently ≈81% |
| 199 | + statement). The gap is concentrated: `cli/main.py` (443 uncovered statements, |
| 200 | + 50%) and `mcp/server.py` (394, 30%) hold ~41% of all uncovered statements, |
| 201 | + followed by `backends/infoblox/bloxone.py` (137, 53%), `core/invoke.py` |
| 202 | + (103, 55%), `backends/infoblox/nios.py` (102, 70%), and |
| 203 | + `backends/cloud_dns.py` (65, 53%). Approach: typer `CliRunner` tests for the |
| 204 | + CLI command surface, MCP tool-handler tests against the mock backend, and |
| 205 | + mocked-HTTP tests for the two Infoblox backends and Cloud DNS (the |
| 206 | + `test_cloudflare_backend.py` pattern already exists). Raise the |
| 207 | + `--cov-fail-under` gate as each tranche lands. |
| 208 | +- **G4 — Reproducible builds (1 PR).** CI job that builds the wheel/sdist twice |
| 209 | + with `SOURCE_DATE_EPOCH` pinned and fails on binary diff |
| 210 | + (`build_reproducible`; hatchling is reproducible by default, so this is |
| 211 | + expected to pass immediately and serve as the criterion's evidence URL). |
| 212 | +- **G5 — Fuzzing (1 PR + application).** Atheris harness in weekly CI, then the |
| 213 | + OSS-Fuzz application (Phase 3.3). Not a Gold criterion, but scheduled here |
| 214 | + because the harness reuses G3's test fixtures. |
| 215 | +- **G6 — Security review (owner: @ivanglabbeek, external ask).** Request a |
| 216 | + review from the Infoblox product-security team — reviewers must be |
| 217 | + independent of the dev team for `security_review` to count. Scope: the |
| 218 | + assurance case (Phase 2.3), SSRF/input-validation paths, DNSSEC/DANE trust |
| 219 | + handling, and release pipeline. Deliverable: a written report linked from |
| 220 | + SECURITY.md, with findings triaged as issues. |
| 221 | +- **G7 — Starter tasks (ongoing).** Label `good first issue` tasks |
| 222 | + (`small_tasks`) — also feeds the contributor pipeline that G8 depends on. |
| 223 | +- **G8 — Unassociated contributors (open blocker).** Candidates exist in the |
| 224 | + pipeline (IETF draft co-authors, ARD-ecosystem developers). The criterion |
| 225 | + needs two *significant* contributors not associated with Infoblox — track |
| 226 | + candidate progress in the LF-graduation issue and revisit at each release. |
| 227 | + Everything else in this plan proceeds independently. |
| 228 | + |
| 229 | +## OSPS Security Baseline |
| 230 | + |
| 231 | +The badge entry also carries the OSPS Baseline questionnaire (`OSPS-*` criteria, |
| 232 | +all unanswered). Nearly every control maps to evidence produced by Phases 0–3 |
| 233 | +(MFA, branch protection, SAST, dependency policy, release signing, vuln |
| 234 | +process). Fill it in **after** Phase 3 lands so the answers are all "Met" on |
| 235 | +first submission. |
| 236 | + |
| 237 | +## Sequencing and ownership |
| 238 | + |
| 239 | +| When | Items | Owner | |
| 240 | +| --- | --- | --- | |
| 241 | +| Now (settings, no PR) | G1: org require-2FA, `enforce_admins`; Phase 0 badge-URL fix | @ivanglabbeek; @iracic82 for the badge entry | |
| 242 | +| Release cycle 1 | Phase 2 PRs (roadmap, assurance case, SECURITY/GOVERNANCE paragraphs, `--cov-fail-under=79`, signed tags); G2 review standards; G4 repro-build job; Phase 3 token-perms + pip pins; Silver questionnaire pass | maintainers; @iracic82 for the questionnaire | |
| 243 | +| Release cycle 1–2 | G3 coverage push to 90/80 (CLI → MCP server → Infoblox/Cloud DNS backends); G5 fuzz harness + OSS-Fuzz application; G6 security review request → report | maintainers; Infoblox security team for G6 | |
| 244 | +| After cycle 2 | Claim `two_person_review` (needs a cycle of enforced history); Gold questionnaire pass; OSPS Baseline pass | @iracic82 (form), maintainers (evidence) | |
| 245 | +| Unscheduled | G8 `contributors_unassociated` — pipeline candidates tracked per release | project lead | |
| 246 | + |
| 247 | +Silver is achievable within release cycle 1. Every Gold criterion except |
| 248 | +`contributors_unassociated` is scheduled above; that criterion aligns exactly |
| 249 | +with the existing LF-graduation recruiting goals in MAINTAINERS.md, so no new |
| 250 | +organizational commitments are introduced by this proposal. |
0 commit comments