Enterprise Readiness Assessment Production/enterprise tier only — see . When to Use - Production/enterprise readiness evaluations - Supply chain security: SLSA provenance, cosign signing, SBOMs - CI/CD hardening, workflow permissions - OpenSSF Best Practices (Passing/Silver/Gold), OSPS Baseline (L1/2/3) - Scorecard optimization (Token-Permissions, Branch-Protection, Pinned-Deps) - Code review, ADRs, changelogs, SECURITY.md Assessment Workflow 1. Discovery : Identify platform, languages, existing CI/CD, dependabot.yml 2. Scoring : Apply checklists; check Scorecard, badge criteria, coverage 3.…

| grep -qE '^(9[0-9]|100)

Enterprise Readiness Assessment Production/enterprise tier only — see . When to Use - Production/enterprise readiness evaluations - Supply chain security: SLSA provenance, cosign signing, SBOMs - CI/CD hardening, workflow permissions - OpenSSF Best Practices (Passing/Silver/Gold), OSPS Baseline (L1/2/3) - Scorecard optimization (Token-Permissions, Branch-Protection, Pinned-Deps) - Code review, ADRs, changelogs, SECURITY.md Assessment Workflow 1. Discovery : Identify platform, languages, existing CI/CD, dependabot.yml 2. Scoring : Apply checklists; check Scorecard, badge criteria, coverage 3.…

\"\n severity: warning\n desc: \"Enterprise projects should have minMsi >= 90% for mutation testing\"\n\n - id: ER-72\n type: command\n pattern: \"cat infection.json5 infection.json 2>/dev/null | grep -oE '\\\"minCoveredMsi\\\"[[:space:]]*:[[:space:]]*[0-9]+' | grep -oE '[0-9]+

Enterprise Readiness Assessment Production/enterprise tier only — see . When to Use - Production/enterprise readiness evaluations - Supply chain security: SLSA provenance, cosign signing, SBOMs - CI/CD hardening, workflow permissions - OpenSSF Best Practices (Passing/Silver/Gold), OSPS Baseline (L1/2/3) - Scorecard optimization (Token-Permissions, Branch-Protection, Pinned-Deps) - Code review, ADRs, changelogs, SECURITY.md Assessment Workflow 1. Discovery : Identify platform, languages, existing CI/CD, dependabot.yml 2. Scoring : Apply checklists; check Scorecard, badge criteria, coverage 3.…

| grep -qE '^(9[0-9]|100)

Enterprise Readiness Assessment Production/enterprise tier only — see . When to Use - Production/enterprise readiness evaluations - Supply chain security: SLSA provenance, cosign signing, SBOMs - CI/CD hardening, workflow permissions - OpenSSF Best Practices (Passing/Silver/Gold), OSPS Baseline (L1/2/3) - Scorecard optimization (Token-Permissions, Branch-Protection, Pinned-Deps) - Code review, ADRs, changelogs, SECURITY.md Assessment Workflow 1. Discovery : Identify platform, languages, existing CI/CD, dependabot.yml 2. Scoring : Apply checklists; check Scorecard, badge criteria, coverage 3.…

\"\n severity: warning\n desc: \"Enterprise projects should have minCoveredMsi >= 90%\"\n\n # === COVERAGE CONFIGURATION ===\n # ER-73: pass unless codecov.yml exists AND its patch.target is exactly 100%.\n # Pure-pipeline form (no ; && || $()) for the allowlist. Pattern semantics:\n # 1. cat codecov.yml (silent if absent)\n # 2. sed extracts the value(s) following `target:` inside the patch block\n # 3. head -1 keeps the first occurrence\n # 4. ! grep -qF '100%' negates: pass when 100% is NOT present\n # No file => empty pipeline => grep finds nothing => ! makes it pass.\n - id: ER-73\n type: command\n pattern: \"! cat codecov.yml 2>/dev/null | sed -n '/patch:/,/target:/{ s/.*target:[[:space:]]*//p }' | head -1 | grep -qF '100%'\"\n severity: warning\n desc: \"codecov patch target should not be 100% — achievable only with full functional test coverage upload\"\n\nllm_reviews:\n # === ACTION PINNING (companion to ER-10) ===\n - id: ER-10b\n domain: security\n prompt: |\n Check GitHub Actions pinning in workflow files:\n 1. Third-party actions (e.g., actions/checkout, codecov/codecov-action) MUST be pinned to SHA\n 2. Org-internal reusable workflows (uses: org/repo/.github/workflows/*.yml@ref) MUST use @main or @tag — this is CORRECT and REQUIRED, NOT a finding\n 3. Flag any third-party action using a tag (e.g., @v4) or branch instead of SHA\n 4. Do NOT flag org-internal reusable workflows using @main — SHA-pinning them is an ANTI-PATTERN that breaks centralized security update propagation\n 5. If an org-internal reusable workflow IS SHA-pinned, flag THAT as a warning (defeats the purpose of centralized workflow management)\n severity: warning\n desc: \"Verify third-party actions are SHA-pinned. Org-internal reusable workflows MUST use @main or @tag (pinning them is an anti-pattern).\"\n\n # === SIGNED COMMITS ===\n - id: ER-16\n domain: security\n prompt: |\n Check if the repository requires signed commits:\n 1. Look for branch protection settings mentioning signatures\n 2. Check if .github/workflows mention commit signing\n 3. Check CONTRIBUTING.md for signing requirements. CONTRIBUTING.md is\n a community-health file that GitHub auto-inherits from the org-wide\n \u003cowner>/.github repo ONLY when the project has NO local\n CONTRIBUTING.md. The fallback does NOT supplement a present-but-\n incomplete local file. Apply this rule:\n - If the project has NO local CONTRIBUTING.md and the org-wide\n \u003cowner>/.github/CONTRIBUTING.md (e.g., netresearch/.github)\n documents the required signing/commit conventions, treat the\n requirement as satisfied. Do NOT flag absence of a project-local\n CONTRIBUTING.md on its own in this case.\n - If the project HAS a local CONTRIBUTING.md, evaluate it on its\n own merits. The org-wide file is NOT consulted by GitHub in this\n case and MUST NOT be used to suppress a finding. If the local\n CONTRIBUTING.md does not document signing requirements, flag it\n regardless of org-wide content.\n - Only flag absence outright when neither the project nor (when\n applicable per the fallback rule above) the org-wide repo\n addresses signing requirements.\n Note: CODEOWNERS does NOT inherit from org-wide .github — it must\n live in the repo itself if needed.\n severity: warning\n desc: \"Repository should require signed commits\"\n\n # === HARDEN-RUNNER REVIEW ===\n - id: ER-27b\n domain: security\n prompt: |\n Verify Harden-Runner configuration in workflow files:\n 1. Every job should have step-security/harden-runner as its FIRST step\n 2. Check egress-policy: prefer 'block' with allowed-endpoints over 'audit'\n 3. Verify harden-runner is SHA-pinned (not tag-referenced)\n 4. If block mode: verify allowed-endpoints are appropriate for the job type\n severity: warning\n desc: \"Verify Harden-Runner is correctly configured in all workflow jobs with appropriate egress policy\"\n\n # === PHPUNIT VERSION COMPATIBILITY ===\n - id: ER-34\n domain: testing\n prompt: |\n Check for PHPUnit version compatibility issues:\n 1. Review composer.json for PHPUnit version constraints — broad ranges like \"^11.0 || ^12.0\" risk cross-version attribute failures\n 2. Check test files for PHPUnit 12-only attributes (e.g., #[AllowMockObjectsWithoutExpectations]) that break on PHPUnit 11\n 3. Verify CI matrix includes the lowest supported PHP version, which may pull an older PHPUnit\n 4. Flag any PHPUnit attribute not available in the minimum supported PHPUnit version\n severity: warning\n desc: \"PHPUnit attributes and features must be compatible with all PHPUnit versions resolved across the CI PHP version matrix\"\n\n # === SLSA PROVENANCE REVIEW ===\n - id: ER-17\n domain: security\n prompt: |\n Verify SLSA provenance setup if present:\n 1. Preferred: actions/attest-build-provenance in a reusable workflow\n hosted in the org .github repo (SLSA Build Level 3)\n 2. Legacy: slsa-github-generator — should be migrated away due to\n SHA-pinning incompatibility (slsa-framework/slsa-github-generator#4440)\n 3. If using actions/attest: verify it's SHA-pinned and has\n id-token: write + attestations: write permissions\n 4. Verification should use `gh attestation verify` (not slsa-verifier)\n severity: info\n desc: \"SLSA provenance should use actions/attest-build-provenance (preferred) or slsa-github-generator\"\n\n # === PHPSTAN BASELINE CONTENT QUALITY ===\n - id: ER-35\n domain: quality\n prompt: |\n If PHPStan is not configured in the project (no phpstan.neon, no phpstan in composer.json require-dev), report N/A and skip this check.\n Review PHPStan baseline file for quality:\n 1. Check if phpstan-baseline.neon exists and contains ignoreErrors entries\n 2. An empty baseline (ignoreErrors: []) is the ideal state — all issues fixed\n 3. Flag any baseline with >0 entries as needing review\n 4. Check if baseline entries are legitimate (complex generics, third-party types) vs. suppressions of real bugs\n 5. Flag any entry that suppresses error-level issues (should be fixed, not suppressed)\n 6. Verify the project is not adding NEW baseline entries (baseline should only shrink over time)\n severity: warning\n desc: \"PHPStan baseline should be empty (ignoreErrors: []). All static analysis issues should be fixed, not suppressed. Review entries and plan to eliminate them\"\n\n # === REUSABLE WORKFLOW SHA PINNING (complements ER-10b for reusable workflows) ===\n - id: ER-36\n domain: security\n prompt: |\n Review reusable workflow references (pattern: uses: org/repo/.github/workflows/file.yml@ref) in .github/workflows/:\n This checkpoint complements ER-10b (which covers GitHub Actions) by specifically targeting reusable workflow references.\n 1. Third-party reusable workflows (external organizations) MUST be SHA-pinned for supply chain security\n 2. Org-internal reusable workflows (same GitHub org) MUST use @main or @tag, NOT SHA-pinned\n 3. SHA-pinning org-internal workflows is an anti-pattern — it breaks centralized security update propagation\n 4. Flag: third-party workflow using @v tag without SHA (e.g., uses: external/repo/.github/workflows/ci.yml@v2)\n 5. Flag: org-internal workflow using SHA pin (e.g., uses: myorg/workflows/.github/workflows/ci.yml@abc123)\n 6. Correct patterns:\n - Third-party: uses: external/repo/.github/workflows/ci.yml@abc123def456 # v2.1.0\n - Org-internal: uses: myorg/workflows/.github/workflows/ci.yml@main\n severity: warning\n desc: \"Third-party reusable workflows MUST use SHA pins. Org-internal reusable workflows MUST use @main/@tag for centralized security updates\"\n\n # === CI DUPLICATE RUN PREVENTION ===\n - id: ER-30\n domain: ci\n prompt: |\n Check GitHub Actions workflow files for duplicate CI run issues:\n 1. Find workflows that trigger on BOTH push: and pull_request:\n 2. If push: is present WITHOUT branch restrictions (e.g., branches: [main]),\n pushing to a PR branch causes every job to run TWICE\n 3. Flag workflows where push: is unrestricted while pull_request: is also present\n 4. Do NOT flag workflows where push: is scoped to specific branches (e.g., push: branches: [main])\n 5. Do NOT flag workflows that only have one trigger\n severity: warning\n desc: \"Workflows with both push and pull_request triggers must scope push to specific branches to prevent duplicate CI runs\"\n\n # === REUSABLE WORKFLOW SHA-PINNING ===\n - id: ER-33\n domain: security\n prompt: |\n Check reusable workflow references (uses: org/repo/.github/workflows/file.yml@ref):\n 1. Third-party reusable workflows (external organizations) MUST be SHA-pinned\n 2. Org-internal reusable workflows MUST use @main or @tag (NOT SHA-pinned)\n 3. A mechanical regex cannot distinguish org-internal from third-party — use context\n 4. Flag: third-party workflow using @v tag without SHA\n 5. Flag: org-internal workflow using SHA pin (anti-pattern)\n severity: warning\n desc: \"Third-party reusable workflows must be pinned to commit SHAs, not tags, for supply chain security\"\n\n # === NPM DEPENDENCY MONITORING ===\n - id: ER-37\n name: npm audit runs in CI when package.json exists\n type: llm_review\n severity: warning\n domain: supply-chain\n prompt: |\n If package.json exists in the project root or Build/ directory:\n 1. Check that CI workflow runs `npm audit` or equivalent\n 2. Check that .github/dependabot.yml includes npm ecosystem\n 3. Check that npm dependencies are not excessively outdated (>2 major versions)\n tags: [npm, supply-chain, dependencies, ci]\n\n - id: ER-38\n name: Dependabot configured for all dependency ecosystems\n type: llm_review\n severity: info\n domain: supply-chain\n prompt: |\n Check .github/dependabot.yml covers ALL dependency ecosystems in use:\n - composer (if composer.json exists)\n - npm (if package.json exists)\n - github-actions (if .github/workflows/ exists)\n - docker (if Dockerfile exists)\n Report any missing ecosystem configurations.\n tags: [dependabot, supply-chain, dependencies]\n\n # === CI ROBUSTNESS: DOCKER / WORKTREE ===\n\n - id: ER-39\n name: CaptainHook disabled in Docker/CI Composer commands\n type: llm_review\n severity: warning\n domain: ci\n prompt: |\n If the project uses CaptainHook (check composer.json for captainhook/captainhook\n or captainhook/plugin-composer):\n 1. Check Build/Scripts/runTests.sh and any CI scripts for `composer install`\n or `composer update` commands run inside Docker containers\n 2. Verify each such command passes CAPTAINHOOK_DISABLE=true as an env variable\n 3. Without this, CaptainHook's hook-installer fails when .git is a file (Docker,\n worktrees), aborting all subsequent post-install-cmd plugins\n 4. Also check docker-compose*.yml / docker-compose*.yaml and compose.yml / compose.yaml\n for Composer commands missing the env var\n If CaptainHook is not in use, report N/A.\n tags: [captainhook, docker, worktree, ci, composer]\n\n - id: ER-40\n name: PHPStan extension-installer generates non-empty config\n type: llm_review\n severity: warning\n domain: quality\n prompt: |\n If the project uses phpstan/extension-installer (check composer.json):\n 1. Check composer.lock / installed packages for PHPStan extension packages\n (e.g., phpstan-doctrine, phpstan-phpunit, phpstan-strict-rules, etc.).\n 2. If such extension packages are installed, GeneratedConfig.php (typically\n vendor/phpstan/extension-installer/src/GeneratedConfig.php) should list\n these registered extensions in the EXTENSIONS constant after composer install.\n 3. If extension packages are present but GeneratedConfig.php shows\n EXTENSIONS = [], the installer likely did not run -- usually because a\n prior Composer plugin (such as CaptainHook) failed and aborted\n post-install-cmd scripts.\n 4. In that case, check that CI scripts include verification of the generated\n config or that CaptainHook is disabled (ER-39) to prevent this failure chain.\n If phpstan/extension-installer is not in use, or no supported extension\n packages are installed, report N/A.\n tags: [phpstan, extension-installer, composer, ci]\n\n - id: ER-41\n name: PHP lint scope excludes vendor and build directories\n type: llm_review\n severity: warning\n domain: ci\n prompt: |\n If the project has a lint test suite or lint CI step:\n 1. Check that PHP lint (php -l) commands scan only source directories\n (e.g., Classes/, Configuration/, Tests/), NOT the entire project tree\n 2. Flag PHP lint commands that traverse third-party or build directories\n (e.g., vendor/, .Build/, var/, node_modules/). A plain `find . -name '*.php'`\n is a finding unless it explicitly excludes those directories (via -prune or ! -path).\n 3. Correct patterns include limiting the start paths to source dirs, e.g.\n `find Classes Configuration Tests -name '*.php'`, or using `find .` with\n appropriate -prune/! -path exclusions for vendor/, .Build/, var/, etc.\n 4. Check Build/Scripts/runTests.sh, Makefile, and CI workflow files\n If no PHP lint step exists, report N/A.\n tags: [lint, php, ci, scope]\n\n - id: ER-42\n name: Merge queue thread resolution awareness\n type: llm_review\n severity: info\n domain: ci\n prompt: |\n If the repository uses GitHub merge queues (check branch protection rules):\n 1. Verify CONTRIBUTING.md or PR templates mention that all review threads\n must be resolved before merge. Both CONTRIBUTING.md and PR templates\n (.github/PULL_REQUEST_TEMPLATE.md) are community-health files that\n GitHub auto-inherits from the org-wide \u003cowner>/.github repo ONLY\n when the project has NO local copy of that specific file. The\n fallback does NOT supplement a present-but-incomplete local file.\n Apply per-file:\n - If the project has NO local CONTRIBUTING.md and the org-wide\n \u003cowner>/.github/CONTRIBUTING.md (e.g., netresearch/.github)\n covers the thread-resolution requirement, treat that requirement\n as satisfied via CONTRIBUTING.md. If the project HAS a local\n CONTRIBUTING.md, evaluate it on its own merits — the org-wide\n file is NOT consulted by GitHub in that case and MUST NOT be\n used to suppress a finding.\n - The same rule applies independently to PR templates. GitHub\n performs a case-insensitive lookup (the filename's actual\n case on disk doesn't matter — GitHub finds it regardless),\n and recognises any of these local paths:\n * .github/PULL_REQUEST_TEMPLATE.md (or .txt form)\n * .github/pull_request_template.md\n * pull_request_template.md (repo root)\n * docs/pull_request_template.md\n * .github/PULL_REQUEST_TEMPLATE/ (directory containing\n multiple templates — ANY file here counts as a local\n template for the purposes of this fallback rule)\n The org-wide template only applies when NONE of these local\n paths exist; if any local template exists, evaluate it\n directly without consulting the org-wide file.\n - Only flag absence outright when neither the project nor (per the\n fallback rule above, where applicable) the org-wide repo\n addresses the thread-resolution requirement via either file.\n CODEOWNERS does NOT inherit from org-wide .github and remains a\n project-local concern if needed.\n 2. Check if auto-merge workflows account for the constraint that commits\n cannot be pushed while a branch is in the merge queue\n 3. Flag if required_review_thread_resolution is enabled but not documented\n If merge queues are not in use, report N/A.\n tags: [merge-queue, github, branch-protection, ci]\n\n # === ADR COVERAGE ===\n - id: ER-45\n name: Architecture Decision Records for significant decisions\n type: llm_review\n severity: warning\n domain: documentation\n prompt: |\n Evaluate whether the project should have Architecture Decision Records (ADRs):\n 1. Count the number of PHP classes in Classes/ — if >10 classes, the extension\n is complex enough to warrant a Documentation/Developer/Adr/ directory\n 2. Scan for significant architectural decisions that should each have an ADR:\n - Encryption or cryptography choices (sodium, openssl, key management)\n - Access control mechanisms (RBAC, permission checks, policy enforcement)\n - Audit logging or compliance-relevant data handling\n - External service integrations or protocol choices\n - Caching strategies or performance-critical design decisions\n - Data model changes with migration implications\n 3. Check if Documentation/Developer/Adr/ directory exists\n 4. If ADRs exist, verify they follow a consistent format (title, status,\n context, decision, consequences)\n Report missing ADRs for identified significant decisions.\n tags: [adr, documentation, architecture, decisions]\n\n # === CHANGELOG COMPLETENESS ===\n - id: ER-46\n name: Every git tag has a corresponding CHANGELOG entry\n type: llm_review\n severity: warning\n domain: documentation\n prompt: |\n Compare git tags with CHANGELOG.md entries:\n 1. Run `git tag -l` to list all version tags, normalizing format by stripping any `v` prefix (e.g., v1.2.0 → 1.2.0)\n 2. Parse CHANGELOG.md headings (## [x.y.z] or ## x.y.z patterns)\n 3. Flag any git tag that does not have a corresponding CHANGELOG heading (compare using normalized version without `v` prefix)\n 4. Flag any CHANGELOG heading that does not correspond to a git tag\n (may indicate an unreleased section — acceptable if labeled [Unreleased])\n 5. Verify CHANGELOG entries are in reverse chronological order\n If CHANGELOG.md does not exist, flag that as the primary finding.\n tags: [changelog, releases, documentation, tags]\n\n # === UPGRADE WIZARD FOR NEW COLUMNS ===\n - id: ER-47\n name: UpgradeWizard for new database columns on existing tables\n type: llm_review\n severity: warning\n domain: migration\n prompt: |\n Check ext_tables.sql for columns added to existing tables and verify\n that appropriate data migration exists:\n 1. Parse ext_tables.sql for CREATE TABLE statements — identify columns\n that are additions to core or existing tables (not entirely new tables)\n 2. For each new column on an existing table, check if an\n UpgradeWizardInterface implementation exists in Classes/Updates/ or\n Classes/Upgrade/ that handles data migration for that column\n 3. New columns with sensible defaults (e.g., NOT NULL DEFAULT '') may\n not need a wizard — focus on columns that require data transformation\n or population from existing data\n 4. Also check for ext_localconf.php registration of upgrade wizards\n Report columns that likely need migration wizards but lack them.\n tags: [upgrade-wizard, database, migration, ext-tables, typo3]\n\n # === REUSABLE WORKFLOWS ONLY ===\n - id: ER-48\n name: CI uses org reusable workflows instead of standalone third-party actions\n type: llm_review\n severity: warning\n domain: ci\n prompt: |\n Review CI workflow files for use of standalone third-party actions where\n org-provided reusable workflows should be used instead:\n 1. Check if workflows directly use third-party actions with unpinned tags\n (e.g., uses: shivammathur/setup-php@v2) instead of calling an org\n reusable workflow that wraps and standardizes these\n 2. Flag workflows that duplicate CI logic already available in org\n reusable workflows (e.g., separate PHPStan, PHP-CS-Fixer, PHPUnit\n steps instead of a single reusable CI workflow call)\n 3. Acceptable: using third-party actions that are SHA-pinned AND not\n available through org reusable workflows\n 4. Preferred pattern: uses: org/.github/.github/workflows/ci.yml@main\n with inputs, rather than reimplementing CI steps locally\n Report specific workflow files and actions that should be replaced\n with org reusable workflow calls.\n tags: [reusable-workflows, ci, github-actions, standardization]\n\n # === BUILD HEALTH ===\n - id: ER-50\n domain: ci\n prompt: |\n Check if the project build produces deprecation warnings or errors:\n 1. Review CI logs or run the build locally to check for deprecation\n warnings that will become errors in future versions\n 2. For TypeScript projects: check tsconfig.json for deprecated options\n (e.g., deprecated tsconfig options that produce TS5101 errors). If found, verify\n the option is actually needed — remove if unused, add\n ignoreDeprecations if required\n 3. For Go projects: check for deprecated function usage flagged by\n go vet or staticcheck\n 4. For PHP projects: check for PHP deprecation notices in CI output\n 5. Build-breaking deprecations in CI indicate the project will fail\n on the next major version upgrade\n Report specific deprecations found and whether they block CI.\n severity: warning\n desc: \"Build should produce no deprecation warnings — these become errors in future versions and block CI\"\n\n # === AUTO-MERGE WORKFLOW PRESENCE ===\n - id: ER-51\n domain: ci\n prompt: |\n Check if the repository has auto-merge configured for dependency PRs:\n 1. Verify .github/workflows/ contains an auto-merge workflow\n (auto-merge-deps.yml or auto-merge.yml)\n 2. If present, verify it uses pull_request_target trigger (not\n pull_request) for correct bot PR permissions\n 3. Verify repo has allow_auto_merge enabled: check via\n gh api repos/OWNER/REPO --jq .allow_auto_merge\n 4. If Dependabot or Renovate is configured but no auto-merge workflow\n exists, bot PRs will accumulate without being merged\n Report missing or misconfigured auto-merge setup.\n severity: warning\n desc: \"Repos with Dependabot/Renovate should have auto-merge workflow to prevent PR accumulation\"\n\n # === REUSABLE WORKFLOW PERMISSION COMPATIBILITY ===\n - id: ER-52\n name: Caller workflows grant all permissions required by called reusable workflows\n type: llm_review\n severity: error\n domain: ci\n prompt: |\n For each workflow in .github/workflows/ calling a reusable workflow at\n `jobs.\u003cjob>.uses` (not step-level `uses`), covering both remote calls\n (`uses: \u003corg>/\u003crepo>/.github/workflows/\u003cfile>@\u003cref>`) and local calls\n (`uses: ./.github/workflows/\u003cfile>.yml`):\n 1. Identify permissions declared in the caller workflow (top-level and job-level)\n 2. GitHub validates ALL job-level permissions at startup, even for skipped jobs\n 3. If the shared workflow declares job-level permissions (e.g., pull-requests: write)\n that are not granted by the caller, the entire workflow fails with startup_failure\n 4. Check: does the caller grant every permission the shared workflow's jobs declare?\n 5. Common miss: caller grants only `contents: write` but shared workflow also needs\n `pull-requests: write` for a bump or changelog job\n 6. Flag missing permissions as error — this causes startup_failure across all repos\n using the shared workflow\n tags: [permissions, reusable-workflows, ci, startup-failure]\n\n # === DUPLICATE RELEASE NOTES DETECTION ===\n - id: ER-53\n name: Release workflow must not produce duplicate content in release notes\n type: llm_review\n severity: warning\n domain: ci\n prompt: |\n Check for duplicate release note issues.\n\n A common cause is a misconfiguration in softprops/action-gh-release.\n\n Static check (primary):\n 1. Read .github/workflows/release.yml or any shared release workflow\n 2. Look for softprops/action-gh-release used with both `body:` (or\n `body_path:`) and `generate_release_notes: true` — this is a known\n misconfiguration where the explicit body is prepended to\n auto-generated notes, producing duplicate content. Flag as warning.\n\n Dynamic confirmation (secondary):\n 3. Check the 3 most recent releases for actual duplication:\n `gh release view \u003ctag> --json body -q .body`\n 4. Look for repeated sections, duplicate changelog entries, or\n duplicated commit lists in release bodies\n tags: [release-notes, duplicate-content, ci, softprops]\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":41715,"content_sha256":"9ad2ddfe679ca400029c1ca7c0b25a2d86a5eb5ba27b725ddbf069fe0c009810"},{"filename":"evals/evals.json","content":"[\n {\n \"name\": \"assess_enterprise_readiness\",\n \"prompt\": \"Assess this project for enterprise readiness and identify gaps\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(OpenSSF|Scorecard|SLSA|SBOM|supply chain)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(ci\\\\.yml|codeql\\\\.yml|scorecard\\\\.yml|dependency-review)\"\n }\n ]\n },\n {\n \"name\": \"setup_slsa_provenance\",\n \"prompt\": \"Set up SLSA provenance for this GitHub repository\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(attest-build-provenance|slsa-github-generator|SLSA Level)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(id-token:\\\\s*write|attestations:\\\\s*write|permissions)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(gh attestation verify|slsa-verifier)\"\n }\n ]\n },\n {\n \"name\": \"openssf_badge_assessment\",\n \"prompt\": \"Check this project's OpenSSF Best Practices Badge status and what criteria are missing\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(bestpractices\\\\.dev|bestpractices\\\\.coreinfrastructure|OpenSSF)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Passing|Silver|Gold|criteria)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(https://|URL)\"\n }\n ]\n },\n {\n \"name\": \"sha_pin_actions\",\n \"prompt\": \"Review and fix GitHub Actions pinning in our workflow files\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(SHA|sha|commit hash|@[a-f0-9]{40})\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(version comment|# v\\\\d)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(third-party|org-internal|reusable workflow)\"\n }\n ]\n },\n {\n \"name\": \"script_injection_prevention\",\n \"prompt\": \"Check our GitHub Actions workflows for script injection vulnerabilities\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(github\\\\.event|\\\\$\\\\{\\\\{|interpolat)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(inputs\\\\.|run:|env:)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(injection|NEVER|environment variable)\"\n }\n ]\n },\n {\n \"name\": \"scorecard_optimization\",\n \"prompt\": \"Our OpenSSF Scorecard is at 6.8. Help us get it above 9.0\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(Token-Permissions|permissions|contents:\\\\s*read)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Branch-Protection|branch protection)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Security-Policy|SECURITY\\\\.md)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Pinned-Dependencies|SHA-pin)\"\n }\n ]\n },\n {\n \"name\": \"setup_mandatory_workflows\",\n \"prompt\": \"Set up all required CI/CD workflows for an enterprise-ready GitHub project\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"ci\\\\.yml\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"codeql\\\\.yml\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"scorecard\\\\.yml\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"dependency-review\\\\.yml\"\n }\n ]\n },\n {\n \"name\": \"setup_mandatory_badges\",\n \"prompt\": \"Add all required enterprise readiness badges to the project README\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(CI Status|actions/workflows)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(codecov\\\\.io|Codecov)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(securityscorecards\\\\.dev|Scorecard)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(bestpractices\\\\.dev|Best Practices)\"\n }\n ]\n },\n {\n \"name\": \"workflow_permissions_hardening\",\n \"prompt\": \"Harden GitHub Actions workflow permissions using least-privilege principle\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(permissions:|contents:\\\\s*read)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(workflow-level|job-level)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(write|least.privilege|minimal)\"\n }\n ]\n },\n {\n \"name\": \"harden_runner_setup\",\n \"prompt\": \"Add step-security/harden-runner to our CI workflows\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(harden-runner|step-security)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(egress|audit|block)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(first step|SHA-pin)\"\n }\n ]\n },\n {\n \"name\": \"dependabot_configuration\",\n \"prompt\": \"Configure Dependabot for comprehensive dependency monitoring in a PHP project with GitHub Actions\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(dependabot\\\\.yml|dependabot)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(composer|github-actions)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(package-ecosystem|auto-merge)\"\n }\n ]\n },\n {\n \"name\": \"code_review_quality\",\n \"prompt\": \"Review this pull request for enterprise-quality code patterns\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(test|resource|instance)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(error handling|edge case|boundary)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(review|pattern|quality)\"\n }\n ]\n },\n {\n \"name\": \"write_adr\",\n \"prompt\": \"Write an Architecture Decision Record for choosing JWT authentication over session-based auth\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(ADR|Architecture Decision Record)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Status|Context|Decision|Consequences)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Accepted|Proposed|Superseded)\"\n }\n ]\n },\n {\n \"name\": \"duplicate_ci_runs\",\n \"prompt\": \"Our CI runs twice on every PR push. Diagnose and fix the duplicate runs\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(push:|pull_request:)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(branches:|branch.restrict|scope)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(duplicate|twice|double)\"\n }\n ]\n },\n {\n \"name\": \"coverage_setup\",\n \"prompt\": \"Set up code coverage reporting with Codecov for a PHP project\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(codecov|coverage)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(upload|codecov-action)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(patch|threshold|codecov\\\\.yml)\"\n }\n ]\n },\n {\n \"name\": \"security_policy\",\n \"prompt\": \"Create a SECURITY.md with vulnerability disclosure process for this project\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(SECURITY\\\\.md|security policy)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(vulnerabilit|disclosure|reporting)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(SLA|response|timeline|days)\"\n }\n ]\n },\n {\n \"name\": \"signed_releases_setup\",\n \"prompt\": \"Set up signed releases with cosign for our container images\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(cosign|GPG|signing)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(verif|attest|signature)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(tag|release|artifact)\"\n }\n ]\n },\n {\n \"name\": \"osps_baseline_assessment\",\n \"prompt\": \"Evaluate this project against OpenSSF OSPS Baseline levels 1, 2, and 3\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(OSPS|Baseline|openssf)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Level 1|Level 2|Level 3|level)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(criteria|requirement|check)\"\n }\n ]\n },\n {\n \"name\": \"auto_merge_setup\",\n \"prompt\": \"Set up auto-merge for Dependabot and Renovate PRs in our repository\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(auto.merge|auto-merge)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(Dependabot|Renovate|dependency)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(pull_request_target|allow_auto_merge|workflow)\"\n }\n ]\n },\n {\n \"name\": \"changelog_compliance\",\n \"prompt\": \"Verify our CHANGELOG.md matches git tags and follows best practices\",\n \"assertions\": [\n {\n \"type\": \"content\",\n \"pattern\": \"(CHANGELOG|changelog)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(tag|version|release)\"\n },\n {\n \"type\": \"content\",\n \"pattern\": \"(chronological|Unreleased|heading)\"\n }\n ]\n }\n]\n","content_type":"application/json; charset=utf-8","language":"json","size":9116,"content_sha256":"a179533cbb2af3fd4e16e76a78c9d6b8db57f8b4c55c02b192b987ca6e22cba5"},{"filename":"references/2fa-enforcement.md","content":"# Two-Factor Authentication Enforcement Guide\n\n> OpenSSF Gold Badge requirements: `require_2FA`, `secure_2FA`\n>\n> Projects must require 2FA for contributors with commit/accept rights\n> and encourage secure 2FA methods (hardware tokens, TOTP).\n\n## GitHub Organization 2FA\n\n### Enable Organization-Wide 2FA Requirement\n\n1. Go to **Organization Settings** → **Security** → **Authentication security**\n2. Enable **Require two-factor authentication**\n3. Set grace period for existing members (recommended: 7 days)\n\n```\nSettings → Security → Authentication security\n☑️ Require two-factor authentication for everyone in the organization\n```\n\n### Verify Member Compliance\n\n```bash\n# List members without 2FA (requires admin)\ngh api orgs/YOUR_ORG/members --jq '.[] | select(.two_factor_disabled == true) | .login'\n\n# Organization security overview\ngh api orgs/YOUR_ORG --jq '{\n members: .members_with_two_factor_disabled,\n total: .members_count\n}'\n```\n\n## Repository-Level Protection\n\n### Branch Protection with 2FA\n\n```yaml\n# Recommended branch protection settings\nbranch_protection:\n required_status_checks:\n strict: true\n contexts: [\"ci\", \"security\"]\n enforce_admins: true\n required_pull_request_reviews:\n dismiss_stale_reviews: true\n require_code_owner_reviews: true\n required_approving_review_count: 1\n # Note: GitHub enforces org-level 2FA requirement\n```\n\n### Commit Signing (Additional Security)\n\n2FA protects account access; commit signing protects commit integrity:\n\n```bash\n# Configure GPG signing\ngit config --global commit.gpgsign true\ngit config --global user.signingkey YOUR_KEY_ID\n\n# Or use SSH signing (requires 2FA to add key)\ngit config --global gpg.format ssh\ngit config --global user.signingkey ~/.ssh/id_ed25519.pub\n```\n\n## Secure 2FA Methods\n\n### Recommended (Gold Badge: `secure_2FA`)\n\n| Method | Security Level | Notes |\n|--------|---------------|-------|\n| Hardware Security Key | ⭐⭐⭐⭐⭐ | YubiKey, Titan - phishing resistant |\n| TOTP Authenticator App | ⭐⭐⭐⭐ | Google Auth, Authy, 1Password |\n| GitHub Mobile | ⭐⭐⭐⭐ | Push notifications |\n| Passkeys | ⭐⭐⭐⭐⭐ | Platform authenticators, biometric |\n\n### Not Recommended\n\n| Method | Issues |\n|--------|--------|\n| SMS | Vulnerable to SIM swapping |\n| Email | Single point of failure |\n| Backup codes only | Static, easily compromised |\n\n## Documentation Requirements\n\n### SECURITY.md Section\n\nAdd to your SECURITY.md:\n\n```markdown\n## Contributor Security Requirements\n\n### Two-Factor Authentication\n\nAll contributors with commit access MUST enable two-factor authentication (2FA).\n\n**Required for:**\n- Repository administrators\n- Contributors with write access\n- Code owners\n- Release managers\n\n**Recommended 2FA methods:**\n1. Hardware security keys (YubiKey, Titan Key)\n2. TOTP authenticator apps (not SMS)\n3. Passkeys/Platform authenticators\n\n**Setup instructions:**\n- [GitHub 2FA Setup](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa)\n- [Hardware Key Setup](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication#configuring-two-factor-authentication-using-a-security-key)\n\n### Commit Signing\n\nWe encourage (but do not require) GPG or SSH signed commits.\nSee [Signing Commits](https://docs.github.com/en/authentication/managing-commit-signature-verification).\n```\n\n### CONTRIBUTING.md Section\n\n```markdown\n## Security Requirements\n\nBefore your first contribution:\n\n1. **Enable 2FA** on your GitHub account\n - Required for all contributors with commit access\n - Recommended: Hardware security key or authenticator app\n - [Setup Guide](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa)\n\n2. **Optional: Sign your commits**\n - GPG signing provides non-repudiation\n - [Signing Guide](https://docs.github.com/en/authentication/managing-commit-signature-verification)\n```\n\n## Verification Workflow\n\n### Check 2FA Status in CI\n\n```yaml\n# .github/workflows/security-check.yml\nname: Security Compliance\non:\n pull_request:\n types: [opened, synchronize]\n\njobs:\n check-contributor:\n runs-on: ubuntu-latest\n steps:\n - name: Check PR author 2FA (informational)\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n run: |\n # Note: 2FA status not directly available via API\n # Organization-level 2FA requirement is the enforcement mechanism\n echo \"✅ PR from ${{ github.actor }}\"\n echo \"ℹ️ 2FA enforcement is handled at organization level\"\n```\n\n### Organization Audit\n\n```bash\n#!/bin/bash\n# scripts/audit-2fa.sh\n\nORG=\"your-org\"\n\necho \"=== 2FA Compliance Audit ===\"\n\n# Check org 2FA requirement\nREQUIRED=$(gh api orgs/$ORG --jq '.two_factor_requirement_enabled')\necho \"2FA Required: $REQUIRED\"\n\nif [ \"$REQUIRED\" = \"true\" ]; then\n echo \"✅ Organization requires 2FA\"\nelse\n echo \"❌ Organization does NOT require 2FA\"\n echo \"Action: Enable at Settings → Security → Authentication security\"\nfi\n\n# List admins (should all have 2FA if org requires it)\necho \"\"\necho \"Repository Admins (all should have 2FA):\"\ngh api repos/$ORG/REPO/collaborators --jq '.[] | select(.permissions.admin == true) | .login'\n```\n\n## Badge Criteria Alignment\n\n| Criterion | Requirement | Implementation |\n|-----------|-------------|----------------|\n| `require_2FA` | 2FA required for privileged access | Organization 2FA requirement |\n| `secure_2FA` | Encourage non-SMS 2FA | Document recommended methods |\n| `access_continuity` | Maintain access if contributor unavailable | Multiple admins with 2FA |\n\n## Solo Maintainer Considerations\n\nFor solo maintainers who cannot enforce organization-level 2FA:\n\n```markdown\n## 2FA Statement (Solo Maintainer)\n\nThis project is maintained by a single developer.\n\n**2FA Status:**\n- ✅ Maintainer uses hardware security key (YubiKey)\n- ✅ Backup codes stored securely offline\n- ✅ Recovery email protected with 2FA\n\n**For future contributors:**\n- 2FA will be required before granting write access\n- Hardware keys or authenticator apps recommended\n```\n\n## Resources\n\n- [GitHub 2FA Documentation](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa)\n- [FIDO Alliance - Security Keys](https://fidoalliance.org/)\n- [OpenSSF Badge 2FA Criteria](https://www.bestpractices.dev/en/criteria#2.require_2FA)\n- [NIST Digital Identity Guidelines](https://pages.nist.gov/800-63-3/)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6528,"content_sha256":"c40fe8dbf7a5f9d4282eebd76570f29a799f3c5103a29bb8d5205818b9ae84d0"},{"filename":"references/badge-display.md","content":"# OpenSSF Badge Display Guide\n\n## MANDATORY Badge Requirements\n\n**ALL badges in this document are MANDATORY, not optional.**\n\nEvery project MUST have these badges displayed in README.md:\n\n| Badge | MANDATORY | Action if Missing |\n|-------|-----------|-------------------|\n| CI Status | **YES** | Add workflow badge |\n| Codecov | **YES** | Enable codecov, upload coverage in CI |\n| OpenSSF Scorecard | **YES** | Add scorecard.yml workflow |\n| OpenSSF Best Practices | **YES** | Register at bestpractices.dev, get PROJECT_ID |\n| OpenSSF Baseline | **YES** | Same project ID, separate badge URL (`/baseline`) |\n\n**\"Unknown\" or placeholder badges are NOT acceptable. Fix them immediately.**\n\n---\n\n> OpenSSF Silver Badge requirement: `documentation_achievements`\n>\n> Projects MUST display their OpenSSF Best Practices Badge prominently in the README.\n\n## Badge URLs\n\n### Get Your Badge (MANDATORY STEPS)\n\n1. Register at https://www.bestpractices.dev/\n2. Complete criteria for your target level\n3. Copy badge URL from your project page\n\n### Badge Formats\n\n```markdown\n# Markdown (Recommended for README.md)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/YOUR_PROJECT_ID/badge)](https://www.bestpractices.dev/projects/YOUR_PROJECT_ID)\n\n# HTML\n\u003ca href=\"https://www.bestpractices.dev/projects/YOUR_PROJECT_ID\">\n \u003cimg src=\"https://www.bestpractices.dev/projects/YOUR_PROJECT_ID/badge\" alt=\"OpenSSF Best Practices\">\n\u003c/a>\n\n# reStructuredText (for Python docs)\n.. image:: https://www.bestpractices.dev/projects/YOUR_PROJECT_ID/badge\n :target: https://www.bestpractices.dev/projects/YOUR_PROJECT_ID\n :alt: OpenSSF Best Practices\n```\n\n### Badge Levels\n\n| Level | Visual | Description |\n|-------|--------|-------------|\n| Passing | ![passing](https://www.bestpractices.dev/assets/passing-12d6fbe3a203e06c5b1730a5ede78e8e.svg) | Basic security practices |\n| Silver | ![silver](https://www.bestpractices.dev/assets/silver-c6d8c2a8e0e57be8b1530aa4d6f54a7e.svg) | Advanced governance & security |\n| Gold | ![gold](https://www.bestpractices.dev/assets/gold-0a66c44c15c80c17a0a82cf8ac0f3a9e.svg) | Highest maturity level |\n\n**OSPS Baseline Badge** (Levels 1/2/3 — separate badge, same project):\n\n```markdown\n[![OpenSSF Baseline](https://www.bestpractices.dev/projects/PROJECT_ID/baseline)](https://www.bestpractices.dev/projects/PROJECT_ID)\n```\n\nThe Baseline badge shows the achieved OSPS security level (1, 2, or 3). Both badges should be displayed together.\n\n## README Badge Section\n\n### Recommended Placement\n\n```markdown\n# Project Name\n\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/ID/badge)](https://www.bestpractices.dev/projects/ID)\n[![OpenSSF Baseline](https://www.bestpractices.dev/projects/ID/baseline)](https://www.bestpractices.dev/projects/ID)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ORG/REPO/badge)](https://securityscorecards.dev/viewer/?uri=github.com/ORG/REPO)\n[![Go Report Card](https://goreportcard.com/badge/github.com/ORG/REPO)](https://goreportcard.com/report/github.com/ORG/REPO)\n[![License](https://img.shields.io/github/license/ORG/REPO)](LICENSE)\n\nProject description here...\n```\n\n### Complete Badge Row Example\n\n```markdown\n\u003c!-- Recommended badge order: Security → Quality → Build → License -->\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/ID/badge)](https://www.bestpractices.dev/projects/ID)\n[![OpenSSF Baseline](https://www.bestpractices.dev/projects/ID/baseline)](https://www.bestpractices.dev/projects/ID)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ORG/REPO/badge)](https://securityscorecards.dev/viewer/?uri=github.com/ORG/REPO)\n[![Security Audit](https://img.shields.io/badge/security-audited-green)](SECURITY.md)\n[![codecov](https://codecov.io/gh/ORG/REPO/branch/main/graph/badge.svg)](https://codecov.io/gh/ORG/REPO)\n[![Go Report Card](https://goreportcard.com/badge/github.com/ORG/REPO)](https://goreportcard.com/report/github.com/ORG/REPO)\n[![CI](https://github.com/ORG/REPO/actions/workflows/ci.yml/badge.svg)](https://github.com/ORG/REPO/actions/workflows/ci.yml)\n[![License](https://img.shields.io/github/license/ORG/REPO)](LICENSE)\n```\n\n## Additional Security Badges\n\n### OpenSSF Scorecard\n\n```markdown\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ORG/REPO/badge)](https://securityscorecards.dev/viewer/?uri=github.com/ORG/REPO)\n```\n\n### SLSA Provenance Level\n\n```markdown\n[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev)\n```\n\n### Sigstore Signed\n\n```markdown\n[![Sigstore](https://img.shields.io/badge/sigstore-signed-blue)](https://search.sigstore.dev/?hash=SHA256_HASH)\n```\n\n### Security Policy\n\n```markdown\n[![Security Policy](https://img.shields.io/badge/security-policy-blue)](SECURITY.md)\n```\n\n## Badge Verification\n\n### Automated Badge Check\n\n```yaml\n# .github/workflows/verify-badges.yml\nname: Verify Badges\non:\n schedule:\n - cron: '0 0 * * 0' # Weekly\n workflow_dispatch:\n\njobs:\n check-badges:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Check OpenSSF Badge Status\n run: |\n # Use cache-buster to avoid stale responses\n DATA=$(curl -s \"https://www.bestpractices.dev/projects/YOUR_ID.json?_=$(date +%s)\")\n STATUS=$(echo \"$DATA\" | jq -r '.badge_level')\n echo \"Current badge level: $STATUS\"\n if [ \"$STATUS\" = \"passing\" ] || [ \"$STATUS\" = \"silver\" ] || [ \"$STATUS\" = \"gold\" ]; then\n echo \"✅ Badge status valid\"\n else\n echo \"❌ Badge status issue\"\n exit 1\n fi\n\n - name: Check Scorecard\n run: |\n SCORE=$(curl -s \"https://api.securityscorecards.dev/projects/github.com/ORG/REPO\" | jq -r '.score')\n echo \"Scorecard score: $SCORE\"\n```\n\n### Badge Link Validator\n\n```bash\n#!/bin/bash\n# scripts/verify-badge-links.sh\n\necho \"Checking badge links in README.md...\"\n\n# Extract all badge URLs\nBADGES=$(grep -oP 'https://[^)]+badge[^)]*' README.md)\n\nfor URL in $BADGES; do\n STATUS=$(curl -s -o /dev/null -w \"%{http_code}\" \"$URL\")\n if [ \"$STATUS\" = \"200\" ]; then\n echo \"✅ $URL\"\n else\n echo \"❌ $URL (HTTP $STATUS)\"\n EXIT_CODE=1\n fi\ndone\n\nexit ${EXIT_CODE:-0}\n```\n\n## Tracking Progress\n\n### Progress Badge\n\nFor projects working toward a badge level:\n\n```markdown\n\u003c!-- Show progress toward Silver -->\n[![OpenSSF Progress](https://img.shields.io/badge/OpenSSF-85%25%20toward%20Silver-yellow)](https://www.bestpractices.dev/projects/ID)\n```\n\n### Criteria Checklist in README\n\n```markdown\n## OpenSSF Best Practices Progress\n\n- [x] Passing Level (100%)\n- [x] Security policy documented\n- [x] Signed releases\n- [ ] Silver Level (85%)\n - [x] DCO enforcement\n - [x] 80% test coverage\n - [ ] Two-person review (solo maintainer)\n- [ ] Gold Level (40%)\n - [ ] 90% test coverage\n - [ ] Security audit\n```\n\n## Badge Criteria Alignment\n\n| Criterion | Requirement | How to Display |\n|-----------|-------------|----------------|\n| documentation_achievements | Badge displayed | Add to README.md header |\n| documentation_basics | Project purpose clear | README intro section |\n| achieve_passing | Passing badge earned | Badge shows automatically |\n| achieve_silver | Silver badge earned | Badge shows automatically |\n\n## Resources\n\n- [OpenSSF Best Practices Badge](https://www.bestpractices.dev/)\n- [OSPS Baseline Standard](https://baseline.openssf.org/)\n- [OpenSSF Scorecard](https://securityscorecards.dev/)\n- [Shields.io Custom Badges](https://shields.io/)\n- [SLSA Badge Generator](https://slsa.dev/get-started)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7636,"content_sha256":"a6e27ff250b4d8f4a4e6ba64ee56123e3693a6036fe12b3c4c6be9d89427963a"},{"filename":"references/badge-submission-api.md","content":"# Badge Data Submission Guide\n\n> Practical guide for programmatically submitting OpenSSF Best Practices Badge data.\n\n## Overview\n\nThe bestpractices.dev platform uses a Rails web application. There is no official REST API for\nupdating badge criteria. Submissions are done via authenticated HTML form PATCH requests.\n\n## Submission Script\n\nA Python script can automate badge data submission using session cookies:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Submit OpenSSF Best Practices Badge data.\n\nUsage:\n BADGE_COOKIE='...' python3 submit-badge.py [--dry-run] [--level LEVEL]\n\"\"\"\nimport json\nimport os\nimport re\nimport sys\nimport time\nimport http.cookiejar\nimport urllib.parse\nimport urllib.request\n\nCOOKIE_NAME = '_BadgeApp_session'\nPROJECT_ID = 12345 # Replace with your project ID\nBASE_URL = 'https://www.bestpractices.dev'\n\nAUTH_TOKEN_PATTERN = re.compile(\n r'\u003cinput type=\"hidden\" name=\"authenticity_token\" value=\"([^\"]+)\"'\n)\nCSRF_TOKEN_PATTERN = re.compile(\n r'\u003cmeta name=\"csrf-token\" content=\"([^\"]+)\"'\n)\n\nLEVELS = ['passing', 'silver', 'gold', 'baseline-1', 'baseline-2', 'baseline-3']\n```\n\n### Authentication\n\n1. Log in to bestpractices.dev in your browser\n2. Extract the `_BadgeApp_session` cookie value from browser DevTools\n3. Pass it via environment variable OR file (file recommended due to shell quoting issues):\n\n```python\n# RECOMMENDED: Read from file to avoid shell quoting issues with special chars\ncookie_file = '/tmp/badge-cookie.txt'\nif os.path.exists(cookie_file):\n with open(cookie_file) as f:\n cookie = f.read().strip()\nelse:\n cookie = os.environ.get('BADGE_COOKIE', '')\n```\n\n### Submission Flow\n\nFor each level:\n\n1. **GET** the edit page to obtain CSRF tokens and lock version\n2. **PATCH** the level URL with form-encoded data\n\n```\nGET /en/projects/{ID}/{level}/edit → Extract authenticity_token + lock_version\nPATCH /en/projects/{ID}/{level} → Submit form data with tokens\n```\n\n**IMPORTANT**: The level path segment is required for ALL levels including passing.\nUse `/en/projects/{ID}/passing/edit`, NOT `/en/projects/{ID}/edit`.\n\n---\n\n## Critical Gotchas\n\n### 1. Cookie Handling - Do NOT URL-Decode, Use File\n\n**CRITICAL**: The session cookie must be sent **exactly as received** from the browser. Do NOT URL-decode it.\n\n**RECOMMENDED**: Write cookie to a file instead of passing via environment variable. Session cookies\noften contain `+`, `/`, `=`, and other characters that break shell quoting.\n\n```python\n# BEST - read from file (no shell quoting issues)\ncookie = open('/tmp/badge-cookie.txt').read().strip()\n\n# OK - environment variable (beware of shell quoting)\ncookie = os.environ.get('BADGE_COOKIE', '')\n\n# WRONG - URL decoding breaks session authentication\ncookie = urllib.parse.unquote(os.environ.get('BADGE_COOKIE', ''))\n```\n\n**Symptom**: Writes appear to succeed (302 redirect) but data is silently not persisted.\n\n### 2. URL-Required Justifications\n\nMany criteria require `https://` URLs in the justification text. Without URLs, criteria show\n\"Warning: URL required, but no URL found\" even when status is \"Met\".\n\n**CRITICAL**: Even with status \"Met\", an **empty justification** or one **without URLs** causes\nthe criterion to show \"Unknown required information, not enough for a badge\" on the edit page.\nThe API will still report the status as \"Met\", but the badge percentage will NOT count it.\n\n**Commonly affected criteria** (require URLs in justification at ALL levels):\n\n| Level | Criteria Requiring URLs |\n|-------|------------------------|\n| Passing | `contribution_requirements`, `report_process`, `vulnerability_report_process`, `vulnerability_report_private`, `tests_documented_added`, `warnings_strict`, `static_analysis_common_vulnerabilities`, `dynamic_analysis_unsafe` |\n| Silver | `dco`, `governance`, `roles_responsibilities`, `access_continuity`, `bus_factor`, `documentation_architecture`, `documentation_achievements`, `vulnerability_report_credit`, `coding_standards`, `external_dependencies` |\n| Gold | `bus_factor`, `contributors_unassociated`, `hardened_site`, `hardening` |\n\n**Rule of thumb**: Always include at least one `https://` URL in every justification text.\n\n```json\n{\n \"dco_justification\": \"DCO enforced via GitHub Actions: https://github.com/org/repo/blob/main/.github/workflows/dco.yml\"\n}\n```\n\n### 3. Auto-Detected Fields Cannot Be Set\n\nThese fields are auto-detected by the platform and **cannot** be set via form submission:\n\n| Field | Why |\n|-------|-----|\n| `homepage_url_status` | Auto-detected from project URL |\n| `report_url_status` | Auto-detected from issue tracker URL |\n| `report_url` | Set at project creation, not a form field |\n\n**Attempting to submit these fields causes HTTP 400 errors.**\n\nRemove them from your badge data files before submission.\n\n**Note**: These fields show as `?` in the API but do NOT count against badge percentages.\n\n### 4. API Response Caching - Use Correct URL Path\n\n**CRITICAL**: The `/en/projects/{ID}.json` path (with locale prefix) returns **aggressively cached**\ndata that can be stale for hours. The `/projects/{ID}.json` path (without locale) returns fresh data.\n\n```bash\n# WRONG - returns stale cached data\ncurl -s \"https://www.bestpractices.dev/en/projects/PROJECT_ID.json\"\n\n# CORRECT - returns fresh data\ncurl -s \"https://www.bestpractices.dev/projects/PROJECT_ID.json?_=$(date +%s)\"\n```\n\n**Symptom**: After successful submission (302 redirect), the API still shows old values.\nSwitching from `/en/projects/` to `/projects/` path immediately shows updated data.\n\n### 5. OSPS Baseline Field Names\n\nOSPS Baseline criteria use a different naming convention in HTML forms:\n\n| Display Format | Form Field Format |\n|---------------|-------------------|\n| `OSPS-AC-01.01` | `osps_ac_01_01_status` |\n| `OSPS-BR-03.02` | `osps_br_03_02_status` |\n\nPattern: All lowercase, hyphens and dots become underscores.\n\n**Note**: OSPS criteria do NOT affect Passing/Silver/Gold badge percentages. They are a separate\nbadge program (OSPS Baseline).\n\n### 6. Session Cookie Rotation\n\nThe server rotates session cookies via `Set-Cookie` headers. Your script must use `CookieJar`\nto automatically track cookie updates:\n\n```python\ndef make_opener(cookie):\n \"\"\"Create urllib opener with cookie jar and no-redirect handling.\"\"\"\n cj = http.cookiejar.CookieJar()\n c = http.cookiejar.Cookie(\n version=0, name='_BadgeApp_session', value=cookie,\n port=None, port_specified=False,\n domain='www.bestpractices.dev', domain_specified=True,\n domain_initial_dot=False,\n path='/', path_specified=True,\n secure=True, expires=None, discard=True,\n comment=None, comment_url=None, rest={}, rfc2109=False,\n )\n cj.set_cookie(c)\n\n class NoRedirectHandler(urllib.request.HTTPErrorProcessor):\n def http_response(self, request, response):\n return response\n https_response = http_response\n\n return urllib.request.build_opener(\n urllib.request.HTTPCookieProcessor(cj),\n NoRedirectHandler,\n )\n```\n\n### 7. Rate Limiting\n\nAdd delays between level submissions to avoid rate limiting:\n\n```python\ntime.sleep(3) # Wait 3 seconds between submissions\n```\n\n---\n\n## Lock Version Extraction\n\n**CRITICAL**: The `lock_version` hidden field is required for successful saves. Without it,\nsubmissions may silently fail (302 redirect but no data persisted).\n\n**The HTML attribute order varies** - sometimes `name` comes before `value`, sometimes after.\nUse a regex that handles both orderings:\n\n```python\n# CORRECT - handles both attribute orderings\nLOCK_VERSION_PATTERN = re.compile(\n r'(?:name=\"project\\[lock_version\\]\"[^>]*value=\"([^\"]*)\"|'\n r'value=\"(\\d+)\"[^>]*name=\"project\\[lock_version\\]\")'\n)\nmatch = LOCK_VERSION_PATTERN.search(html)\nlock_version = (match.group(1) or match.group(2)) if match else None\n\n# WRONG - only handles one attribute ordering\nre.search(r'name=\"project\\[lock_version\\]\"[^>]*value=\"([^\"]*)\"', html)\n```\n\n---\n\n## Detecting Insufficient Criteria\n\nThe badge edit page contains `\u003cimg>` elements with `id=\"{criterion}_enough\"` that indicate\nwhether each criterion meets badge requirements. Use this to find what's actually blocking\nthe badge percentage:\n\n```python\n# After fetching the edit page HTML:\nall_enough = re.findall(r'\u003cimg[^>]*id=\"(\\w+)_enough\"[^>]*alt=\"([^\"]+)\"', html)\nnot_enough = [(name, alt) for name, alt in all_enough if 'not' in alt.lower() or 'Not' in alt]\n\nfor name, alt in not_enough:\n print(f\" {name}: {alt}\")\n```\n\n**Common \"not enough\" messages:**\n- `\"Not enough for a badge.\"` — Status is \"Unmet\" or criteria not satisfied\n- `\"Unknown required information, not enough for a badge.\"` — Status is \"Met\" but justification\n is empty or missing required URL\n\n---\n\n## Badge Data File Format\n\nStructure badge data as JSON files per level:\n\n```\nbadge-data-passing.json\nbadge-data-silver.json\nbadge-data-gold.json\nbadge-data-baseline-1.json\nbadge-data-baseline-2.json\nbadge-data-baseline-3.json\n```\n\nEach file contains field name/value pairs:\n\n```json\n{\n \"criterion_name_status\": \"Met\",\n \"criterion_name_justification\": \"Explanation with https://github.com/org/repo/evidence\"\n}\n```\n\nValid status values: `Met`, `Unmet`, `N/A`, `?` (unknown)\n\n**Note**: Some criteria only accept specific status values:\n- `dynamic_analysis_enable_assertions_status`: Only `Met`, `Unmet`, `?` (NOT `N/A`)\n- `access_continuity_status`: Only `Met`, `Unmet`, `?` (NOT `N/A`)\n- `bus_factor_status`: Only `Met`, `Unmet`, `?` (NOT `N/A`)\n- `contributors_unassociated_status`: Only `Met`, `Unmet`, `?` (NOT `N/A`)\n- `two_person_review_status`: Only `Met`, `Unmet`, `?` (NOT `N/A`)\n\n---\n\n## Multi-Project Batch Submission\n\nFor submitting badge data across multiple projects, use a configuration dict:\n\n```python\nPROJECTS = {\n 'project-name': {\n 'id': 12345,\n 'levels': {\n 'passing': '/path/to/badge-data-passing.json',\n 'silver': '/path/to/badge-data-silver.json',\n 'gold': '/path/to/badge-data-gold.json',\n }\n },\n # ... more projects\n}\n\n# Submit all\nfor name, config in PROJECTS.items():\n for level, data_file in config['levels'].items():\n if os.path.exists(data_file):\n submit_level(opener, config['id'], level, data_file)\n time.sleep(3)\n```\n\n---\n\n## Solo Maintainer Justification Patterns\n\nFor projects with a single maintainer, use these justification templates:\n\n**`two_person_review`** (set to Met, N/A not allowed):\n```\nThe project uses automated multi-reviewer workflow: GitHub Copilot code review + auto-approve\nbot for solo maintainer. Branch protection requires passing CI + review approval.\nSee: https://github.com/org/repo/blob/main/.github/workflows/pr-quality-gates.yml\n```\n\n**`bus_factor`** (set to Met, N/A not allowed):\n```\nBus factor managed through comprehensive documentation, CI automation, and organizational access.\nOrganization maintains access to all repositories. Backup maintainers have repository access\nvia GitHub organization membership: https://github.com/orgs/ORG/people\n```\n\n**`access_continuity`** (set to Met, N/A not allowed):\n```\nAccess continuity ensured via GitHub organization. Multiple organization members have admin\naccess. Repository settings and credentials managed at organization level:\nhttps://github.com/orgs/ORG/people\n```\n\nSee also: `references/solo-maintainer-guide.md`\n\n---\n\n## Verification\n\nAfter submission, verify via API:\n\n```bash\n# Check badge level (use /projects/ NOT /en/projects/)\ncurl -s \"https://www.bestpractices.dev/projects/PROJECT_ID.json?_=$(date +%s)\" | \\\n jq '{badge_level, badge_percentage_0, badge_percentage_1, badge_percentage_2}'\n\n# badge_percentage_0 = Passing\n# badge_percentage_1 = Silver\n# badge_percentage_2 = Gold\n```\n\nTo verify specific criteria are truly accepted (not just \"Met\" in API):\n\n```python\n# Fetch edit page and check _enough indicators\nhtml = fetch_edit_page(opener, project_id, level)\nnot_enough = re.findall(\n r'\u003cimg[^>]*id=\"(\\w+)_enough\"[^>]*alt=\"([^\"]+)\"',\n html\n)\nblockers = [(n, a) for n, a in not_enough if 'not' in a.lower()]\n```\n\n---\n\n## Troubleshooting\n\n| Problem | Cause | Fix |\n|---------|-------|-----|\n| 302 redirect but data not saved | URL-decoded cookie | Send cookie as-is, no `urllib.parse.unquote()` |\n| 302 redirect but data not saved | Missing lock_version | Extract lock_version from edit page (handle both attr orderings) |\n| HTTP 400 on PATCH | Submitting auto-detected fields | Remove `homepage_url_status`, `report_url_status` from data |\n| Criteria shows `?` despite Met status | Missing URL in justification | Add `https://` URL to justification text |\n| \"Met\" in API but not counting for % | Empty justification or no URL | Badge edit page shows \"Unknown required information\" - add justification with URL |\n| Stale API response after submit | Using `/en/projects/` path | Use `/projects/{ID}.json` (no locale prefix) with cache-buster |\n| GET returns 302 redirect | Cookie expired | Get fresh cookie from browser |\n| 200 with \"form contains N errors\" | Validation errors | Check which criteria allow N/A; some only accept Met/Unmet/? |\n| Silver/Gold data silently lost | Cookie rotation lost in redirect | Use `NoRedirectHandler` + `HTTPCookieProcessor` with `CookieJar` |\n| N/A rejected for specific criteria | Criterion doesn't allow N/A | Check form radio buttons (see list above) |\n| Shell quoting breaks cookie | Special chars (+, /, =) in cookie | Write cookie to file, read in Python |\n| Lock version regex returns None | HTML has reversed attribute order | Use alternation regex for both orderings |\n\n### N/A Not Allowed on Certain Criteria\n\nSome Silver/Gold criteria **do not allow N/A** status. Submitting N/A causes validation errors\nthat silently prevent the entire save (form re-renders with 200 status).\n\n**Passing criteria that DON'T allow N/A:**\n- `dynamic_analysis_enable_assertions` (only Met/Unmet/?)\n\n**Silver criteria that DON'T allow N/A:**\n- `access_continuity` (MUST)\n- `bus_factor` (MUST)\n\n**Gold criteria that DON'T allow N/A:**\n- `contributors_unassociated` (MUST)\n- `two_person_review` (MUST)\n\nAlways check the edit form's radio buttons to verify which values are accepted.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14160,"content_sha256":"baf7ea1c9fff943774c2cfefef50c5ee9bf513a90c1baf6d3ec38aafdac77d09"},{"filename":"references/badges-and-workflows.md","content":"# Mandatory Badges & Workflows\n\n## Mandatory Badges\n\n| Badge | URL Pattern |\n|-------|-------------|\n| CI Status | `github.com/ORG/REPO/actions/workflows/ci.yml/badge.svg` |\n| Codecov | `codecov.io/gh/ORG/REPO/graph/badge.svg` |\n| OpenSSF Scorecard | `api.securityscorecards.dev/projects/github.com/ORG/REPO/badge` |\n| OpenSSF Best Practices | `www.bestpractices.dev/projects/PROJECT_ID/badge` |\n| OpenSSF Baseline | `www.bestpractices.dev/projects/PROJECT_ID/baseline` |\n\n## Mandatory Workflows\n\n| Workflow | File | Purpose |\n|----------|------|---------|\n| CI | `ci.yml` | Build, test, lint |\n| CodeQL | `codeql.yml` | Security scanning |\n| Scorecard | `scorecard.yml` | OpenSSF Scorecard |\n| Dependency Review | `dependency-review.yml` | PR CVE check |\n\n## Scorecard Quick Wins\n\n| Check | Quick Fix | Impact |\n|-------|-----------|--------|\n| Token-Permissions | `permissions: {}` at workflow-level, `write` only per-job | 0->10 |\n| Branch-Protection | `required_approving_review_count: 1` + auto-approve | 0->8 |\n| Security-Policy | SECURITY.md + private vulnerability reporting | 4->10 |\n| Pinned-Dependencies | SHA-pin all actions (SLSA generator pinnable but internal actions use tag refs) | 8->10 |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1207,"content_sha256":"421c4628ddf2c82e1291e486938cda722095acedd6598fed5ec07c6f84030033"},{"filename":"references/branch-coverage.md","content":"# Branch Coverage Guide\n\n> OpenSSF Badge Criteria: `test_branch_coverage80` (Gold - requires 80% branch coverage)\n\n## Understanding Branch vs Statement Coverage\n\n### Statement Coverage\nMeasures which lines of code are executed:\n```go\nfunc Example(x int) string {\n if x > 0 { // Line executed\n return \"pos\" // Line executed OR not\n }\n return \"neg\" // Line executed OR not\n}\n```\n**100% statement coverage** = all lines executed at least once.\n\n### Branch Coverage (Decision Coverage)\nMeasures whether each decision point (if/else, switch) has been tested for all outcomes:\n```go\nfunc Example(x int) string {\n if x > 0 { // Branch: true AND false\n return \"pos\"\n }\n return \"neg\"\n}\n```\n**100% branch coverage** = both `x > 0` (true) AND `x \u003c= 0` (false) tested.\n\n### Why Branch Coverage Matters\n\nStatement coverage can be 100% while missing critical paths:\n```go\nfunc Process(a, b bool) {\n if a && b {\n // Critical path\n }\n}\n\n// This test achieves 100% statement coverage\nfunc TestProcess(t *testing.T) {\n Process(true, true) // Executes the if block\n Process(false, false) // Skips the if block\n}\n// But misses: (true, false) and (false, true) branches\n```\n\n---\n\n## Measuring Branch Coverage in Go\n\n### Native Go Coverage (Statement Only)\n\nGo's built-in coverage tool measures statement coverage:\n```bash\ngo test -coverprofile=coverage.out ./...\ngo tool cover -func=coverage.out # Shows statement coverage\n```\n\n### Approach 1: gocover-cobertura + XML Analysis\n\nConvert Go coverage to Cobertura XML format:\n\n```bash\n# Install\ngo install github.com/boumenot/gocover-cobertura@latest\n\n# Generate\ngo test -coverprofile=coverage.out -covermode=count ./...\ngocover-cobertura \u003c coverage.out > coverage.xml\n```\n\nThen analyze with tools that support branch metrics.\n\n### Approach 2: gocov + gocov-xml\n\n```bash\n# Install\ngo install github.com/axw/gocov/gocov@latest\ngo install github.com/AlekSi/gocov-xml@latest\n\n# Generate\ngo test -coverprofile=coverage.out ./...\ngocov convert coverage.out | gocov-xml > coverage.xml\n```\n\n### Approach 3: Codecov Branch Analysis\n\nCodecov can analyze branch coverage:\n\n```yaml\n# .github/workflows/coverage.yml\n- name: Run tests\n run: go test -coverprofile=coverage.out -covermode=atomic ./...\n\n- name: Upload to Codecov\n uses: codecov/codecov-action@v4\n with:\n files: coverage.out\n flags: unittests\n fail_ci_if_error: true\n```\n\nIn Codecov settings, enable branch coverage reports.\n\n---\n\n## Achieving 80% Branch Coverage\n\n### Strategy 1: Test All Conditional Outcomes\n\nFor every `if` statement, ensure both true and false branches are tested:\n\n```go\nfunc Validate(s string) error {\n if s == \"\" {\n return errors.New(\"empty\")\n }\n if len(s) > 100 {\n return errors.New(\"too long\")\n }\n return nil\n}\n\nfunc TestValidate(t *testing.T) {\n tests := []struct {\n name string\n input string\n wantErr bool\n }{\n {\"empty string\", \"\", true}, // if s == \"\" → true\n {\"valid string\", \"hello\", false}, // if s == \"\" → false\n {\"too long\", strings.Repeat(\"x\", 101), true}, // if len > 100 → true\n {\"at limit\", strings.Repeat(\"x\", 100), false}, // if len > 100 → false\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := Validate(tt.input)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"Validate(%q) error = %v, wantErr %v\", tt.input, err, tt.wantErr)\n }\n })\n }\n}\n```\n\n### Strategy 2: Cover Compound Conditions\n\nFor `&&` and `||` conditions, test each combination:\n\n```go\nfunc IsEligible(age int, hasLicense bool) bool {\n if age >= 18 && hasLicense {\n return true\n }\n return false\n}\n\nfunc TestIsEligible(t *testing.T) {\n tests := []struct {\n age int\n license bool\n want bool\n }{\n {20, true, true}, // Both true\n {20, false, false}, // First true, second false\n {16, true, false}, // First false, second true\n {16, false, false}, // Both false\n }\n // Run all test cases...\n}\n```\n\n### Strategy 3: Cover Switch Cases\n\nTest all switch cases including default:\n\n```go\nfunc Categorize(score int) string {\n switch {\n case score >= 90:\n return \"A\"\n case score >= 80:\n return \"B\"\n case score >= 70:\n return \"C\"\n default:\n return \"F\"\n }\n}\n\nfunc TestCategorize(t *testing.T) {\n tests := []struct {\n score int\n want string\n }{\n {95, \"A\"}, // First case\n {85, \"B\"}, // Second case\n {75, \"C\"}, // Third case\n {65, \"F\"}, // Default case\n }\n // Run all test cases...\n}\n```\n\n### Strategy 4: Test Error Paths\n\nError handling often has low coverage:\n\n```go\nfunc ReadConfig(path string) (*Config, error) {\n data, err := os.ReadFile(path)\n if err != nil {\n return nil, fmt.Errorf(\"read file: %w\", err) // Often untested\n }\n\n var cfg Config\n if err := json.Unmarshal(data, &cfg); err != nil {\n return nil, fmt.Errorf(\"parse config: %w\", err) // Often untested\n }\n\n return &cfg, nil\n}\n\nfunc TestReadConfig(t *testing.T) {\n // Happy path\n t.Run(\"valid config\", func(t *testing.T) {\n cfg, err := ReadConfig(\"testdata/valid.json\")\n require.NoError(t, err)\n require.NotNil(t, cfg)\n })\n\n // Error path: file not found\n t.Run(\"file not found\", func(t *testing.T) {\n _, err := ReadConfig(\"nonexistent.json\")\n require.Error(t, err)\n require.Contains(t, err.Error(), \"read file\")\n })\n\n // Error path: invalid JSON\n t.Run(\"invalid json\", func(t *testing.T) {\n _, err := ReadConfig(\"testdata/invalid.json\")\n require.Error(t, err)\n require.Contains(t, err.Error(), \"parse config\")\n })\n}\n```\n\n---\n\n## CI/CD Enforcement\n\n### Check and Fail on Low Coverage\n\n```yaml\n# .github/workflows/coverage.yml\n- name: Check branch coverage\n run: |\n go test -coverprofile=coverage.out -covermode=atomic ./...\n\n # Statement coverage as proxy (actual branch coverage requires external tools)\n COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%')\n\n # For branch coverage, multiply by 0.85-0.9 factor\n # (branch coverage is typically 85-90% of statement coverage)\n ESTIMATED_BRANCH=$(awk -v c=\"$COVERAGE\" 'BEGIN { printf \"%.1f\", c * 0.85 }')\n\n echo \"Statement coverage: $COVERAGE%\"\n echo \"Estimated branch coverage: $ESTIMATED_BRANCH%\"\n\n if awk -v b=\"$ESTIMATED_BRANCH\" 'BEGIN { exit !(b >= 80) }'; then\n echo \"✓ Branch coverage meets 80% threshold\"\n else\n echo \"✗ Branch coverage below 80% threshold\"\n exit 1\n fi\n```\n\n### Using Codecov with Branch Coverage\n\n```yaml\n# codecov.yml\ncoverage:\n precision: 2\n round: down\n range: \"70...100\"\n status:\n project:\n default:\n target: 80%\n threshold: 1%\n branches:\n - main\n patch:\n default:\n target: 80%\n threshold: 1%\n\n# Comment settings\ncomment:\n layout: \"reach, diff, flags, files\"\n behavior: default\n require_changes: true\n require_base: true\n require_head: true\n branches:\n - main\n```\n\n---\n\n## Common Pitfalls\n\n### 1. Testing Only Happy Paths\n\n```go\n// Bad: Only tests success\nfunc TestProcess_Success(t *testing.T) {\n result, _ := Process(validInput)\n assert.Equal(t, expected, result)\n}\n\n// Good: Tests all outcomes\nfunc TestProcess(t *testing.T) {\n t.Run(\"success\", func(t *testing.T) { ... })\n t.Run(\"invalid input\", func(t *testing.T) { ... })\n t.Run(\"timeout\", func(t *testing.T) { ... })\n}\n```\n\n### 2. Ignoring Default Cases\n\n```go\n// Ensure switch default is covered\nswitch status {\ncase Active: ...\ncase Pending: ...\ndefault:\n // This path is often missed\n return fmt.Errorf(\"unknown status: %s\", status)\n}\n```\n\n### 3. Short-Circuit Evaluation\n\n```go\n// if a || b: need tests where a=true, and where a=false,b=true, and where both false\n// if a && b: need tests where both true, a=false, and a=true,b=false\n```\n\n---\n\n## Tools Comparison\n\n| Tool | Branch Coverage | Language | Free |\n|------|-----------------|----------|------|\n| Codecov | Yes (with config) | Multi | Freemium |\n| Coveralls | Limited | Multi | Freemium |\n| SonarQube | Yes | Multi | Freemium |\n| gocov | No (statement only) | Go | Yes |\n| JaCoCo | Yes | Java | Yes |\n| Istanbul | Yes | JS/TS | Yes |\n| Coverage.py | Yes (with branch flag) | Python | Yes |\n\n---\n\n## Badge Criteria Verification\n\nTo verify `test_branch_coverage80`:\n\n1. **Generate coverage report**\n ```bash\n go test -coverprofile=coverage.out -covermode=atomic ./...\n ```\n\n2. **Convert to analyzable format**\n ```bash\n gocover-cobertura \u003c coverage.out > coverage.xml\n ```\n\n3. **Check branch coverage**\n - Upload to Codecov/Coveralls\n - Or use local XML analysis tools\n - Or estimate from statement coverage (* 0.85)\n\n4. **Document in badge application**\n - Link to coverage reports\n - Show coverage percentage\n - Explain measurement methodology\n\n---\n\n## Resources\n\n- [Go Coverage Documentation](https://go.dev/blog/cover)\n- [Codecov Branch Coverage](https://docs.codecov.com/docs/about-code-coverage#branch-coverage)\n- [gocover-cobertura](https://github.com/boumenot/gocover-cobertura)\n- [Branch vs Statement Coverage](https://www.atlassian.com/continuous-delivery/software-testing/code-coverage)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9443,"content_sha256":"d5b057b3244977d71f99282f3704dc578fc8eaa672631d7d5eb8dd94a6c59134"},{"filename":"references/ci-docker-worktree.md","content":"# CI Robustness in Docker/Worktree Environments\n\nPatterns and fixes for CI pipelines that run inside Docker containers\nor git worktree checkouts, where common assumptions about the `.git`\ndirectory structure break.\n\n## 1. CaptainHook in Docker\n\nCaptainHook's hook-installer plugin fails in Docker containers and git\nworktrees where `.git` is a file (pointing to the bare repo) instead\nof a directory. The failure aborts Composer's `post-install-cmd`\nscripts, silently preventing subsequent plugins from running.\n\n### Fix\n\nAdd `CAPTAINHOOK_DISABLE=true` to every Docker-based Composer command:\n\n```bash\ndocker run ... -e CAPTAINHOOK_DISABLE=true ... composer install\n```\n\nIn `runTests.sh` or similar CI scripts, pass the variable into the\ncontainer environment:\n\n```bash\ndocker compose run -e CAPTAINHOOK_DISABLE=true php composer install --no-interaction\n```\n\n### Detection\n\nFind Composer commands that are missing the guard:\n\n```bash\ngrep -n \"composer\" Build/Scripts/runTests.sh | grep -v \"CAPTAINHOOK_DISABLE\"\n```\n\nAny match indicates a command that will fail when `.git` is not a\ndirectory (Docker, worktrees, CI shallow clones).\n\n## 2. PHPStan Extension Installer Verification\n\n`phpstan/extension-installer` registers extensions during Composer's\n`post-install-cmd` phase. If an earlier plugin (such as CaptainHook)\naborts that phase, extension-installer never runs, and PHPStan\noperates without the extensions it needs.\n\n### Symptom\n\nPHPStan reports false positives or misses errors that extensions\nshould catch (e.g., PHPStan for Doctrine, phpstan-phpunit).\n\n### Verification\n\nAfter `composer install`, check the generated config:\n\n```bash\n# Prefer TYPO3-style .Build/vendor if present; otherwise use vendor/ or $VENDOR_DIR\nVENDOR_DIR=\"${VENDOR_DIR:-vendor}\"\nif [ -d \".Build/vendor\" ]; then\n SEARCH_PATH=\".Build/vendor/phpstan/extension-installer/src/GeneratedConfig.php\"\nelse\n SEARCH_PATH=\"$VENDOR_DIR/phpstan/extension-installer/src/GeneratedConfig.php\"\nfi\n\ngrep -F \"EXTENSIONS = []\" \"$SEARCH_PATH\"\n```\n\nIf the output shows `EXTENSIONS = []`, the extension-installer plugin\ndid not execute. This is almost always caused by CaptainHook (or\nanother plugin) failing before extension-installer could run.\n\n### Fix\n\nApply the CaptainHook disable fix from Section 1, then re-run\n`composer install`. Verify that `GeneratedConfig.php` now lists the\nexpected extensions.\n\n## 3. Lint Scope\n\nPHP lint (`php -l`) test suites must scan only project source\ndirectories, not `vendor/` or `.Build/`. Third-party packages may\ncontain intentional syntax error fixtures (e.g., for their own parser\ntests), which cause false lint failures.\n\n### Wrong -- scans vendor too\n\n```bash\nfind . -name '*.php' ! -path './.Build/*' -exec php -l {} \\;\n```\n\nThis still includes `vendor/` at the project root (if present) and\nmisses the intent of excluding build artifacts.\n\n### Acceptable -- `find .` with proper exclusions\n\n```bash\nfind . -name '*.php' \\\n ! -path './vendor/*' ! -path './.Build/*' ! -path './var/*' ! -path './node_modules/*' \\\n -exec php -l {} \\;\n```\n\nUsing `find .` is fine when it explicitly prunes or excludes\nthird-party and build directories.\n\n### Best -- scan only source directories\n\n```bash\nfind Classes Configuration Tests -name '*.php' -exec php -l {} \\;\n```\n\nExplicitly list the directories that contain project code. This is\nfaster, deterministic, and immune to third-party fixture files.\n\n### Detection\n\n```bash\ngrep -n 'find \\.' Build/Scripts/runTests.sh | grep -Ei 'php.*-l|lint'\n```\n\nIf the `find` starts from `.` without `-prune` or `! -path` exclusions\nfor vendor/, .Build/, var/, etc., the lint scope is too broad.\n\n## 4. Merge Queue Awareness\n\nWhen branch protection uses GitHub merge queues, several constraints\napply that can surprise CI workflows:\n\n| Constraint | Effect |\n|-----------|--------|\n| `required_review_thread_resolution: true` | Blocks merge if any review thread is unresolved |\n| Queue locks the branch | Commits cannot be pushed to a branch while it is in the merge queue |\n| Auto-merge interaction | Auto-merge must be disabled before pushing fixes to a PR, then re-enabled after |\n\n### Workflow\n\n1. Address all review comments and resolve threads\n2. If the PR has auto-merge enabled and needs fixes:\n - Disable auto-merge\n - Push the fix commits\n - Wait for CI\n - Re-enable auto-merge\n3. Ensure all review threads are resolved before the PR enters the\n queue\n\n### Detection\n\nCheck whether a repository uses merge queues:\n\n```bash\ngh api repos/{owner}/{repo}/rules/branches/main \\\n --jq '.[].parameters.merge_queue_enabled // false'\n```\n\n## Related References\n\n- `references/ci-patterns.md` -- CI/CD pipeline design and Git hooks\n- `references/general.md` -- Universal enterprise readiness checks\n- `references/security-hardening.md` -- Security scanning in CI\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4810,"content_sha256":"f9edf9286d6c2d74c92ca41895eedea5d973aa149ce5ba54ab3a5de96e2f2cb6"},{"filename":"references/ci-patterns.md","content":"# CI/CD Patterns\n\nComprehensive CI/CD patterns for enterprise-ready projects.\nCovers Git hooks, pipeline design, and quality gates.\n\n## 1. Git Hooks Strategy\n\n### Pre-commit vs Pre-push Division\n\n**Principle:** Fast feedback locally, thorough verification before sharing.\n\n| Hook | Purpose | Speed Target | What to Run |\n|------|---------|--------------|-------------|\n| **pre-commit** | Catch obvious issues | \u003c 5 seconds | Format, lint, build, secrets |\n| **pre-push** | Thorough verification | \u003c 2 minutes | Full test suite, security scans |\n\n### Lefthook Configuration Example\n\n```yaml\n# .lefthook.yml\n\npre-commit:\n parallel: true\n commands:\n fmt:\n glob: \"*.{go,php,ts,js}\"\n run: |\n # Language-specific formatters\n gofmt -w {staged_files} 2>/dev/null || true\n prettier --write {staged_files} 2>/dev/null || true\n\n lint:\n glob: \"*.{go,php,ts}\"\n run: |\n golangci-lint run --new-from-rev=HEAD~1 || true\n phpstan analyse --no-progress || true\n eslint {staged_files} || true\n\n build:\n run: |\n go build ./... || make build\n\n secrets:\n run: gitleaks protect --staged --no-banner\n\npre-push:\n parallel: true\n commands:\n test:\n run: |\n go test -race ./...\n # Or: phpunit, npm test, etc.\n\n lint-full:\n run: golangci-lint run --timeout 5m\n\n security:\n run: |\n govulncheck ./...\n # Or: composer audit, npm audit, etc.\n```\n\n### Hook Bypass Policy\n\n- **Never** bypass hooks in normal workflow\n- Use `--no-verify` only for:\n - Emergency hotfixes (document why)\n - WIP commits on feature branches (squash before PR)\n- CI must run same checks as hooks (no bypass possible)\n\n## 2. Comprehensive CI Pipeline\n\n### Pipeline Structure\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ CI Pipeline │\n├─────────────────────────────────────────────────────────────┤\n│ Stage 1: Fast Feedback (parallel) │\n│ ├── lint (golangci-lint, phpstan, eslint) │\n│ ├── format-check (gofmt, prettier) │\n│ ├── build │\n│ └── secrets-scan (gitleaks, trufflehog) │\n├─────────────────────────────────────────────────────────────┤\n│ Stage 2: Testing (parallel by OS/version) │\n│ ├── unit-tests (ubuntu, macos, windows) │\n│ ├── integration-tests │\n│ ├── fuzz-tests (if applicable) │\n│ └── benchmarks (optional, for tracking) │\n├─────────────────────────────────────────────────────────────┤\n│ Stage 3: Security (parallel) │\n│ ├── sast (CodeQL, semgrep) │\n│ ├── dependency-scan (govulncheck, npm audit) │\n│ ├── container-scan (trivy, if applicable) │\n│ └── license-check │\n├─────────────────────────────────────────────────────────────┤\n│ Stage 4: Coverage & Quality (depends on tests) │\n│ ├── coverage-report │\n│ └── quality-gate (fail if below threshold) │\n├─────────────────────────────────────────────────────────────┤\n│ Stage 5: Artifacts (depends on all above) │\n│ ├── build-artifacts │\n│ └── attestations (SLSA provenance) │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### Preventing Duplicate CI Runs\n\nWhen a workflow triggers on both `push:` and `pull_request:`, pushing to a PR branch\ncauses every job to run **twice** -- once for the push event and once for the\npull_request event. This wastes CI minutes and clutters the checks list.\n\n**Fix:** Restrict `push:` to protected branches only:\n\n```yaml\non:\n push:\n branches: [main] # Only run on pushes to main (merge commits)\n pull_request: # Runs on PR open/sync — covers feature branches\n```\n\n**Anti-pattern** (causes duplicate runs):\n```yaml\non:\n push: # BAD: triggers on ALL branch pushes including PR branches\n pull_request:\n```\n\nThis applies to all workflow files, not just `ci.yml`. Audit every `.github/workflows/*.yml`\nfor unscoped `push:` triggers.\n\n### GitHub Actions Example\n\n```yaml\nname: CI\n\non:\n push:\n branches: [main]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n # Stage 1: Fast Feedback\n lint:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1\n with:\n version: latest\n\n secrets-scan:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n with:\n fetch-depth: 0\n - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.8\n env:\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n # Stage 2: Testing (matrix)\n test:\n strategy:\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n go-version: ['1.25.x']\n runs-on: ${{ matrix.os }}\n steps:\n - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n with:\n go-version: ${{ matrix.go-version }}\n - run: go test -race -coverprofile=coverage.txt ./...\n - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2\n if: matrix.os == 'ubuntu-latest'\n\n # Stage 3: Security\n codeql:\n runs-on: ubuntu-latest\n permissions:\n security-events: write\n steps:\n - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n - uses: github/codeql-action/init@v3\n with:\n languages: go\n - uses: github/codeql-action/autobuild@v3\n - uses: github/codeql-action/analyze@v3\n\n vulnerability-scan:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0\n - run: go install golang.org/x/vuln/cmd/govulncheck@latest\n - run: govulncheck ./...\n\n # Stage 4: Quality Gate\n quality-gate:\n needs: [lint, test, codeql, vulnerability-scan]\n runs-on: ubuntu-latest\n steps:\n - run: echo \"All checks passed\"\n```\n\n## 3. Quality Gates\n\n### Coverage Thresholds\n\n| Level | Statement Coverage | Branch Coverage | Use Case |\n|-------|-------------------|-----------------|----------|\n| Minimum | 60% | - | Legacy projects |\n| Standard | 80% | - | Most projects |\n| High | 80% | 70% | Critical systems |\n| Enterprise | 90% | 80% | Financial, medical |\n\n### Multi-Suite Coverage (MANDATORY)\n\nCoverage must be collected from ALL test suites, not just unit tests:\n\n```yaml\n# PHP: Collect coverage from each test suite\n- name: Unit Tests\n run: php -d pcov.enabled=1 .Build/bin/phpunit -c Build/phpunit/UnitTests.xml --coverage-clover .Build/coverage/unit.xml\n\n- name: Integration Tests\n run: php -d pcov.enabled=1 .Build/bin/phpunit -c Build/phpunit/IntegrationTests.xml --coverage-clover .Build/coverage/integration.xml\n\n- name: E2E Tests\n run: php -d pcov.enabled=1 .Build/bin/phpunit -c Build/phpunit/E2ETests.xml --coverage-clover .Build/coverage/e2e.xml\n\n# Upload ALL coverage files\n- uses: codecov/codecov-action@SHA # vX.Y.Z\n with:\n files: .Build/coverage/unit.xml,.Build/coverage/integration.xml,.Build/coverage/e2e.xml\n```\n\n### Polyglot Coverage (PHP + JavaScript)\n\nProjects with both PHP and JavaScript MUST collect coverage from both:\n\n```yaml\n# PHP Tests\n- name: PHP Tests with Coverage\n run: |\n php -d pcov.enabled=1 .Build/bin/phpunit -c Build/phpunit/UnitTests.xml --coverage-clover .Build/coverage/unit.xml\n php -d pcov.enabled=1 .Build/bin/phpunit -c Build/phpunit/IntegrationTests.xml --coverage-clover .Build/coverage/integration.xml\n\n# JavaScript Tests (vitest/jest)\n- uses: actions/setup-node@SHA # vX.Y.Z\n with:\n node-version: '22'\n\n- run: npm install\n\n- name: JavaScript Tests with Coverage\n run: npm run test:coverage # Must output coverage/lcov.info\n\n# Upload ALL coverage\n- uses: codecov/codecov-action@SHA # vX.Y.Z\n with:\n files: .Build/coverage/unit.xml,.Build/coverage/integration.xml,coverage/lcov.info\n```\n\n**vitest.config.js MUST include lcov reporter:**\n```javascript\ncoverage: {\n provider: 'v8',\n reporter: ['text', 'json', 'html', 'lcov'], // lcov REQUIRED\n reportsDirectory: 'coverage',\n}\n```\n\n### Enforcing Coverage\n\n```yaml\n# In CI workflow\n- name: Check coverage threshold\n run: |\n COVERAGE=$(go tool cover -func=coverage.txt | grep total | awk '{print $3}' | tr -d '%')\n if (( $(echo \"$COVERAGE \u003c 80\" | bc -l) )); then\n echo \"Coverage $COVERAGE% is below 80% threshold\"\n exit 1\n fi\n\n# For patch coverage (new code only)\n- name: Check patch coverage\n run: |\n diff-cover coverage.xml --compare-branch=origin/main --fail-under=90\n```\n\n### Codecov Patch Coverage Configuration\n\nCodecov's `patch` target checks coverage on **new/changed lines only**. Set a\nrealistic threshold -- lines that require specific PHP extensions (GD, Imagick) or\na full TYPO3 bootstrap may be uncoverable in some CI matrix entries but covered in\nothers.\n\n```yaml\n# codecov.yml\ncoverage:\n status:\n project:\n default:\n target: 80%\n patch:\n default:\n target: 60% # Realistic for extension code with environment deps\n threshold: 5% # Allow 5% drop before failing\n```\n\n**Key considerations:**\n- Lines behind `extension_loaded('gd')` guards may show as uncovered in matrix\n entries without that extension\n- TYPO3 integration tests may cover code that unit tests cannot reach\n- Set `patch.target` based on what your CI matrix actually covers, not aspirational goals\n- Use Codecov flags to separate unit vs integration coverage for clearer reporting\n\n### PR Quality Requirements\n\n```yaml\n# Branch protection rules\nbranch_protection:\n required_status_checks:\n strict: true\n contexts:\n - lint\n - test (ubuntu-latest)\n - test (macos-latest)\n - test (windows-latest)\n - codeql\n - vulnerability-scan\n\n required_pull_request_reviews:\n required_approving_review_count: 1\n dismiss_stale_reviews: true\n\n required_linear_history: true # Enforce rebase\n\n required_signatures: true # Signed commits\n```\n\n## 4. PHPStan Baseline Management\n\n### Generating a Baseline\n\nCapture all existing issues so new code is held to a higher standard:\n\n```bash\n# Generate baseline from current state\nvendor/bin/phpstan analyse --generate-baseline\n\n# When codebase is already clean, allow empty baseline\nvendor/bin/phpstan analyse --generate-baseline --allow-empty-baseline\n```\n\nThis creates `phpstan-baseline.neon` (or the configured path) containing all\ncurrently-known errors, which PHPStan then ignores on subsequent runs.\n\n### Baseline CI Strategy\n\nThe baseline should **shrink over time, never grow**. Track this in CI:\n\n```yaml\n- name: PHPStan analysis\n run: vendor/bin/phpstan analyse --no-progress\n\n- name: Check baseline size\n run: |\n if [ -f phpstan-baseline.neon ]; then\n IGNORED=$(grep -c 'message:' phpstan-baseline.neon || echo 0)\n echo \"PHPStan baseline: $IGNORED ignored errors\"\n # Optional: fail if baseline grew\n # Compare against a known count stored in a tracking file\n fi\n```\n\n### Baseline Best Practices\n\n| Practice | Description |\n|----------|-------------|\n| **Goal: empty baseline** | `ignoreErrors: []` means all issues resolved |\n| **Never add new entries** | Fix new issues immediately; only existing code gets a pass |\n| **Review baseline in PRs** | Any PR that increases baseline size needs justification |\n| **Track count over time** | Log baseline error count as a CI metric |\n| **Use `--allow-empty-baseline`** | Required when generating baseline on a clean codebase |\n\n### PHPStan Configuration Example\n\n```neon\n# phpstan.neon\nincludes:\n - phpstan-baseline.neon\n\nparameters:\n level: max\n paths:\n - Classes\n tmpDir: .Build/phpstan\n```\n\n## 5. Multi-Platform Testing\n\n### OS Matrix Strategy\n\n```yaml\nstrategy:\n fail-fast: false # Don't cancel other jobs on failure\n matrix:\n os: [ubuntu-latest, macos-latest, windows-latest]\n include:\n # OS-specific configurations\n - os: ubuntu-latest\n artifact-suffix: linux-amd64\n - os: macos-latest\n artifact-suffix: darwin-amd64\n - os: windows-latest\n artifact-suffix: windows-amd64.exe\n```\n\n### Version Matrix Strategy\n\n```yaml\nstrategy:\n matrix:\n # Test oldest supported and latest\n version: ['1.21', '1.25'] # Or PHP: ['8.1', '8.4']\n include:\n - version: '1.25'\n latest: true # Upload coverage only from latest\n```\n\n## 6. Flaky Test Prevention\n\n### CI-Specific Test Configuration\n\n```yaml\n# Increase timeouts in CI\n- name: Run tests\n run: go test -race -timeout 10m ./...\n env:\n CI: true\n TEST_TIMEOUT_MULTIPLIER: 3\n```\n\n### Retry Strategy for Known Flaky Tests\n\n```yaml\n- name: Run tests with retry\n uses: nick-fields/retry@v3\n with:\n timeout_minutes: 10\n max_attempts: 3\n command: go test -race ./...\n```\n\n### Detecting Flaky Tests\n\n```yaml\n# Run tests multiple times to detect flakiness\n- name: Flaky test detection\n if: github.event_name == 'schedule' # Nightly only\n run: |\n for i in {1..10}; do\n go test -race ./... || exit 1\n done\n```\n\n## 7. Caching Strategy\n\n### Go Caching\n\n```yaml\n- uses: actions/setup-go@v5\n with:\n go-version: '1.25'\n cache: true # Caches go mod and build cache\n```\n\n### PHP Caching\n\n```yaml\n- name: Cache Composer\n uses: actions/cache@v4\n with:\n path: vendor\n key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}\n```\n\n### Node Caching\n\n```yaml\n- uses: actions/setup-node@v4\n with:\n node-version: '22'\n cache: 'npm'\n```\n\n## 8. Signed Commits Enforcement\n\n### Problem: GitHub Can't Sign Rebase Merges\n\nWhen repository requires signed commits AND only allows rebase merge:\n\n```yaml\n# This fails because GitHub can't sign rebased commits\ngh pr merge 123 --rebase\n# Error: Base branch requires signed commits\n```\n\n### Solution: Local Merge Workflow\n\n```bash\n# 1. Fetch and checkout main\ngit checkout main\ngit pull origin main\n\n# 2. Fast-forward merge (preserves signatures)\ngit merge feature-branch --ff-only\n\n# 3. Push to main\ngit push origin main\n```\n\n### Automating with PR Labels\n\n```yaml\n# Workflow triggered by 'ready-to-merge' label\non:\n pull_request:\n types: [labeled]\n\njobs:\n merge:\n if: github.event.label.name == 'ready-to-merge'\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n token: ${{ secrets.MERGE_TOKEN }} # PAT with push access\n\n - name: Merge PR\n run: |\n git checkout main\n git merge origin/${{ github.head_ref }} --ff-only\n git push origin main\n```\n\n## 9. Reusable Workflow Pinning\n\n### Supply Chain Security for Reusable Workflows\n\nAll `uses:` references to **third-party** reusable workflows must be pinned to\ncommit SHAs, just like regular actions:\n\n```yaml\n# BAD: branch reference is mutable (supply chain risk)\njobs:\n ci:\n uses: org/shared-workflows/.github/workflows/ci.yml@main\n\n# BAD: tag reference is also mutable\njobs:\n ci:\n uses: org/shared-workflows/.github/workflows/ci.yml@v2\n\n# GOOD: SHA-pinned (immutable reference)\njobs:\n ci:\n uses: org/shared-workflows/.github/workflows/ci.yml@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 # v2.1.0\n```\n\n### Exception: Org-Internal Workflows\n\nOrg-internal reusable workflows (maintained by the same organization) **should use\n`@main` or `@tag`**, not SHAs. SHA-pinning org-internal workflows is an\n**anti-pattern** because it breaks centralized security update propagation.\n\nSee `checkpoints.yaml` ER-33 for the enforcement rule.\n\n### Auditing Workflow References\n\n```bash\n# Find all reusable workflow references\ngrep -rn 'uses:.*\\.github/workflows/.*\\.yml@' .github/workflows/\n\n# Find unpinned references (tag or branch, no SHA)\ngrep -rn 'uses:.*\\.github/workflows/.*\\.yml@' .github/workflows/ | grep -v '@[a-f0-9]\\{40\\}'\n```\n\n## Related References\n\n- `references/ci-docker-worktree.md` - Docker/worktree CI robustness (CaptainHook, lint scope, merge queues)\n- `references/security-hardening.md` - Security scanning details\n- `references/code-review.md` - Code quality checklist\n- `references/signed-releases.md` - Release signing\n- `assets/workflows/` - Ready-to-use workflow templates\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17889,"content_sha256":"7156e37610fc01681facbc3c1fa7b0e8dd6325f06640fbf3b9dd300ace0cbe5f"},{"filename":"references/code-review.md","content":"# Code Review Quality Patterns\n\nLanguage-agnostic code review checklist and patterns for ensuring production-quality code.\nThese patterns apply to Go, PHP, TypeScript, Python, and other languages.\n\n## Code Review Checklist\n\nBefore approving any PR, verify these patterns:\n\n### 1. Test Resource Management\n\n**Issue:** Tests creating multiple instances of the same resource when only one is needed.\n\n**Check:**\n- [ ] Is there exactly ONE instance of each configurable resource per test?\n- [ ] Is the resource fully configured at creation time (not reconfigured later)?\n- [ ] Are shared counters/state isolated between test resources?\n\n**Bad Example:**\n```go\nfunc TestFeature(t *testing.T) {\n // BAD: Creates two instances, first is unused\n c := NewClient(WithTimeout(5))\n _, _ = c.AddJob(\"* * * * *\", myJob)\n\n c = NewClient(WithTimeout(5), WithLogger(logger)) // Replaced!\n _, _ = c.AddJob(\"* * * * *\", myJob)\n c.Start()\n}\n```\n\n**Good Example:**\n```go\nfunc TestFeature(t *testing.T) {\n // GOOD: Single instance, fully configured\n c := NewClient(\n WithTimeout(5),\n WithLogger(logger),\n )\n _, _ = c.AddJob(\"* * * * *\", myJob)\n c.Start()\n}\n```\n\n### 2. State Mutation Completeness\n\n**Issue:** Operations that logically change state but don't update tracking fields.\n\n**Check:**\n- [ ] After operations complete, are ALL related tracking fields updated?\n- [ ] On restart/retry, will the state be correct?\n- [ ] Are side effects committed atomically with the main operation?\n\n**Bad Example:**\n```go\nfunc (s *Scheduler) handleMissedRuns(entry *Entry, missed []time.Time) {\n for _, t := range missed {\n s.runJob(entry, t)\n }\n // BAD: entry.LastRun not updated - will re-run on restart!\n}\n```\n\n**Good Example:**\n```go\nfunc (s *Scheduler) handleMissedRuns(entry *Entry, missed []time.Time) {\n for _, t := range missed {\n s.runJob(entry, t)\n }\n // GOOD: Update tracking state after execution\n entry.LastRun = missed[len(missed)-1]\n}\n```\n\n### 3. Defensive Enum/Const Handling\n\n**Issue:** Switch statements on enums without handling unknown values.\n\n**Check:**\n- [ ] Does the enum type have a `Valid()` or `IsValid()` method?\n- [ ] Do switch statements have a `default` case?\n- [ ] Is the default case tested explicitly?\n- [ ] Are unexpected values logged for debugging?\n\n**Bad Example:**\n```go\nfunc (p Policy) Apply() {\n switch p {\n case PolicyA:\n doA()\n case PolicyB:\n doB()\n // BAD: No default case - silently ignores invalid values\n }\n}\n```\n\n**Good Example:**\n```go\n// Valid returns true if the policy is a known valid value\nfunc (p Policy) Valid() bool {\n return p >= PolicyA && p \u003c= PolicyB\n}\n\nfunc (p Policy) Apply() error {\n switch p {\n case PolicyA:\n doA()\n case PolicyB:\n doB()\n default:\n // GOOD: Log and handle unexpected values\n log.Warn(\"unexpected policy value\", \"policy\", int(p))\n return fmt.Errorf(\"invalid policy: %d\", p)\n }\n return nil\n}\n```\n\n**Testing Default Case:**\n```go\nfunc TestPolicyApply_InvalidPolicy(t *testing.T) {\n p := Policy(99) // Invalid value\n err := p.Apply()\n if err == nil {\n t.Error(\"expected error for invalid policy\")\n }\n}\n```\n\n### 4. Documentation Accuracy\n\n**Issue:** Documentation claims that don't match actual behavior or benchmarks.\n\n**Check:**\n- [ ] Are performance claims verified against actual benchmark output?\n- [ ] Are negative trade-offs documented (memory increase, complexity)?\n- [ ] Do code examples include all necessary imports?\n- [ ] Do code examples handle errors appropriately?\n\n**Bad Example:**\n```markdown\n## Performance\nWithCapacity provides ~6% improvement in bulk operations.\n```\n(When benchmarks actually show 12% improvement)\n\n**Good Example:**\n```markdown\n## Performance\nWithCapacity provides ~12% improvement in bulk addition time (based on\nBenchmarkBulkAdd: 128342 ns/op without vs 113338 ns/op with capacity).\n\nTrade-off: ~3% higher memory usage due to pre-allocated capacity.\n```\n\n### 5. Platform-Specific Code\n\n**Issue:** Code that only works on certain platforms without documentation.\n\n**Check:**\n- [ ] Is platform-specific code clearly marked?\n- [ ] Are limitations documented in comments?\n- [ ] Are alternatives provided for unsupported platforms?\n- [ ] Are build tags used where appropriate?\n\n**Bad Example:**\n```go\nfunc lockFile(f *os.File) error {\n return syscall.Flock(int(f.Fd()), syscall.LOCK_EX)\n}\n```\n\n**Good Example:**\n```go\n// lockFile acquires an exclusive lock on the file.\n// NOTE: This uses flock() which is Unix-specific (Linux, macOS, BSD).\n// On Windows, use LockFileEx from golang.org/x/sys/windows.\n//\n//go:build !windows\nfunc lockFile(f *os.File) error {\n return syscall.Flock(int(f.Fd()), syscall.LOCK_EX)\n}\n```\n\n### 6. Defensive Code Coverage\n\n**Issue:** Defensive code paths (error handling, edge cases) not tested.\n\n**Check:**\n- [ ] Are `default` switch cases covered by tests?\n- [ ] Are error paths tested (not just happy path)?\n- [ ] Are boundary conditions tested?\n- [ ] Is validation logic tested with invalid inputs?\n\n**Strategy for Testing Defensive Code:**\n```go\n// To test defensive code that's normally unreachable,\n// bypass normal validation and call internal functions directly\n\nfunc TestHandleInvalidState(t *testing.T) {\n // Create object in invalid state (bypassing constructor validation)\n obj := &MyObject{\n state: InvalidState(99),\n }\n\n // Call method that has defensive handling\n err := obj.Process()\n\n // Verify defensive code executed correctly\n if err == nil {\n t.Error(\"expected error for invalid state\")\n }\n}\n```\n\n## Review Process Quality\n\n### Required Conversation Resolution (Branch Protection)\n\nEnable `required_conversation_resolution` in branch protection to **enforce** that all review threads are resolved before merging. Without this, review feedback can be silently ignored — especially problematic when automated reviewers (e.g., GitHub Copilot) leave actionable comments.\n\n```bash\n# Check if enabled\ngh api repos/{owner}/{repo}/branches/main/protection \\\n --jq 'if .required_conversation_resolution.enabled then \"✅ Enabled\" else \"❌ NOT enabled\" end'\n```\n\nSee `references/github.md` → \"Review Enforcement\" for full setup instructions.\n\n### PR Review Thread Resolution\n\nWhen addressing review feedback:\n\n1. **Fix the code** - Address the feedback\n2. **Reply to the thread** - Explain what was changed (use GraphQL API to reply to thread, not new comment)\n3. **Resolve the thread** - Mark as resolved after fix is pushed\n4. **Verify CI** - Ensure all checks still pass\n\n### Review Comment Quality\n\nGood review comments:\n- Explain WHY something is problematic\n- Provide a concrete suggestion or example\n- Reference documentation or standards when applicable\n\n## Language-Specific Implementations\n\n| Pattern | Go | PHP | TypeScript |\n|---------|-----|-----|------------|\n| Enum validation | `func (e Enum) Valid() bool` | `enum_exists()` or cases match | `Object.values(Enum).includes(v)` |\n| Time mocking | `FakeClock` interface | `Carbon::setTestNow()` | `jest.useFakeTimers()` |\n| Platform detection | Build tags `//go:build` | `PHP_OS_FAMILY` | `process.platform` |\n\n## Integration with CI\n\nAdd these checks to your CI pipeline:\n\n```yaml\n# Example: Ensure test coverage for new code\n- name: Check patch coverage\n run: |\n # Fail if new code has \u003c 90% coverage\n diff-cover coverage.xml --fail-under=90\n```\n\n## Related References\n\n- `references/documentation.md` - Documentation standards\n- `references/ci-patterns.md` - CI/CD automation\n- `references/security-hardening.md` - Security patterns\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7664,"content_sha256":"f84b7eb7ed798fd064246c8bb37892aafe1ee19b3b6ec925be70f8e557cc1e3f"},{"filename":"references/cve-workflow.md","content":"# Dependency CVE Workflow\n\nWhen assessing enterprise readiness, **always run dependency audit** as part of discovery.\n\n## Audit Commands\n\n```bash\n# PHP/Composer\ncomposer audit\n\n# Node.js\nnpm audit\n\n# Python\npip-audit\n\n# Go\ngovulncheck ./...\n```\n\n## CVE Handling Best Practice\n\n**Separate dependency updates from code changes:**\n\n| PR Type | Content | Why |\n|---------|---------|-----|\n| Code changes | Business logic, bug fixes, features | Reviewable, testable in isolation |\n| Dependency updates | `composer update`, version bumps | Clear diff, easy rollback if issues |\n\n**Real-world example from t3x-cowriter review:**\n- Found 4 CVEs during enterprise assessment\n- CVE fixes required `composer update typo3/cms-core typo3/cms-backend`\n- Kept separate from code fixes (JS bug, AGENTS.md updates) for clean PR history\n\n## CVE Severity Response\n\n| Severity | Response Time | Action |\n|----------|---------------|--------|\n| CRITICAL | Immediate | Hotfix PR, expedited review |\n| HIGH | 24-48 hours | Priority PR, security review |\n| MEDIUM | 1 week | Normal PR cycle |\n| LOW | Next release | Batch with other updates |\n\n## CI Integration\n\nAdd dependency audit to CI pipeline:\n\n```yaml\n# .github/workflows/ci.yml\n- name: Security audit\n run: composer audit --format=plain\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1276,"content_sha256":"2d64c8da1821914f30ec8df645c664148cda4e90ea6a014948a7f1e6c48e15de"},{"filename":"references/dco-implementation.md","content":"# Developer Certificate of Origin (DCO) Implementation Guide\n\nThe DCO is a lightweight way to certify that contributors have the right to submit their code under the project's license.\n\n## What is DCO?\n\nThe [Developer Certificate of Origin](https://developercertificate.org/) is a legal statement that contributors make when submitting code:\n\n```\nDeveloper Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n have the right to submit it under the open source license\n indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n of my knowledge, is covered under an appropriate open source\n license and I have the right under that license to submit that\n work with modifications, whether created in whole or in part\n by me, under the same open source license; or\n\n(c) The contribution was provided directly to me by some other\n person who certified (a), (b) or (c) and I have not modified it.\n\n(d) I understand and agree that this project and the contribution\n are public and that a record of the contribution is maintained\n indefinitely and may be redistributed consistent with this project\n or the open source license(s) involved.\n```\n\n## Implementation Steps\n\n### 1. Document DCO in CONTRIBUTING.md\n\nAdd this section to your CONTRIBUTING.md:\n\n```markdown\n## Developer Certificate of Origin (DCO)\n\nAll contributions to this project must be signed off using the DCO.\n\n### Sign Your Commits\n\nUse the `-s` flag when committing:\n\n\\`\\`\\`bash\ngit commit -s -m \"feat: add new feature\"\n\\`\\`\\`\n\nThis adds a `Signed-off-by` line to your commit message:\n\n\\`\\`\\`\nfeat: add new feature\n\nSigned-off-by: Your Name \[email protected]>\n\\`\\`\\`\n\n### Retroactive Sign-off\n\nIf you forgot to sign a commit:\n\n\\`\\`\\`bash\n# For the last commit\ngit commit --amend -s\n\n# For multiple commits\ngit rebase --signoff HEAD~N\n\\`\\`\\`\n\n### Configure Git for Automatic Sign-off\n\n\\`\\`\\`bash\ngit config --global alias.cs \"commit -s\"\n\\`\\`\\`\n```\n\n### 2. Add GitHub Action for Enforcement\n\nCreate `.github/workflows/dco.yml`:\n\n```yaml\nname: DCO Check\n\non:\n pull_request:\n branches: [main, master]\n\npermissions:\n contents: read\n pull-requests: read\n\njobs:\n dco:\n name: DCO Check\n runs-on: ubuntu-latest\n steps:\n - name: Check DCO\n uses: dcoapp/app@v1\n```\n\n### 3. Add DCO to PR Template\n\nAdd to `.github/PULL_REQUEST_TEMPLATE.md`:\n\n```markdown\n## DCO Sign-off\n\n- [ ] I have signed off all commits in this PR (`git commit -s`)\n\nBy submitting this PR, I certify that my contribution is made under\nthe terms of the Developer Certificate of Origin.\n```\n\n## Verification\n\n### Check a Commit\n\n```bash\ngit log --format='%h %s%n Signed-off-by: %aN \u003c%aE>' -1\n```\n\n### Verify All Commits in a PR\n\n```bash\ngit log origin/main..HEAD --format='%h %B' | grep -B1 \"Signed-off-by:\"\n```\n\n## Common Issues\n\n### 1. Missing Sign-off\n\n**Error**: \"DCO check failed\"\n\n**Fix**:\n```bash\ngit rebase --signoff HEAD~N\ngit push --force-with-lease\n```\n\n### 2. Email Mismatch\n\n**Error**: \"Email in sign-off doesn't match commit author\"\n\n**Fix**:\n```bash\ngit config user.email \"[email protected]\"\ngit commit --amend -s\n```\n\n### 3. Corporate Email Required\n\nSome projects require corporate email addresses. Configure:\n\n```bash\ngit config user.email \"[email protected]\"\n```\n\n## DCO vs CLA\n\n| Aspect | DCO | CLA |\n|--------|-----|-----|\n| Legal weight | Per-commit attestation | One-time signed agreement |\n| Friction | Low (just `-s` flag) | Higher (legal review) |\n| Implementation | Git hooks/CI | Bot or manual process |\n| Common in | Linux kernel, CNCF | Apache, Google projects |\n\n## OpenSSF Badge Requirements\n\nFor **Silver level**:\n- `dco`: The project MUST have a way to ensure contributors have rights to contribute\n\nThis is satisfied by:\n1. DCO enforcement (recommended)\n2. CLA (Contributor License Agreement)\n3. Clear license in CONTRIBUTING.md\n\n## References\n\n- [Developer Certificate of Origin](https://developercertificate.org/)\n- [DCO GitHub App](https://github.com/dcoapp/app)\n- [Git sign-off documentation](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4296,"content_sha256":"52cd7d3e4c5ce43481746e2484962933997a5ee44f6f0c258727ca767a25d8ec"},{"filename":"references/documentation.md","content":"# Documentation Standards\n\nLanguage-agnostic documentation patterns for enterprise-ready projects.\nCovers ADRs, API references, migration guides, and changelog practices.\n\n## 1. Architecture Decision Records (ADRs)\n\nADRs document significant architectural decisions with their context and consequences.\n\n### When to Create an ADR\n\n- Choosing between multiple valid approaches\n- Making decisions that affect multiple components\n- Establishing patterns that will be followed project-wide\n- Decisions that are hard to reverse\n- Trade-offs that future maintainers need to understand\n\n### ADR Template\n\n```markdown\n# ADR-NNN: Title of Decision\n\n## Status\n[Proposed | Accepted | Deprecated | Superseded by ADR-XXX]\n\n## Date\nYYYY-MM-DD\n\n## Context\n\nWhat is the issue that we're seeing that is motivating this decision?\nWhat forces are at play (technical, political, social)?\n\n## Decision\n\nWhat is the change that we're proposing and/or doing?\n\n## Consequences\n\n### Positive\n- Benefit 1\n- Benefit 2\n\n### Negative\n- Trade-off 1\n- Trade-off 2\n\n### Neutral\n- Side effect that's neither good nor bad\n\n## Alternatives Considered\n\n### Alternative A: [Name]\n- Pros: ...\n- Cons: ...\n- Why rejected: ...\n\n### Alternative B: [Name]\n- Pros: ...\n- Cons: ...\n- Why rejected: ...\n```\n\n### ADR File Organization\n\n```\ndocs/\n└── adr/\n ├── README.md # Index of all ADRs\n ├── ADR-000-template.md # Template for new ADRs\n ├── ADR-001-first-decision.md\n ├── ADR-002-another-decision.md\n └── ...\n```\n\n### ADR Index (README.md)\n\n```markdown\n# Architecture Decision Records\n\n| ADR | Title | Status | Date |\n|-----|-------|--------|------|\n| [ADR-001](ADR-001-example.md) | Use PostgreSQL for persistence | Accepted | 2026-01-15 |\n| [ADR-002](ADR-002-example.md) | Adopt functional options pattern | Accepted | 2026-01-16 |\n```\n\n## 2. API Reference Structure\n\n### Organization Pattern\n\n```\ndocs/\n├── API_REFERENCE.md # Main API reference\n├── GETTING_STARTED.md # Quick start guide\n├── COOKBOOK.md # Common recipes/patterns\n└── guides/\n ├── MIGRATION.md # Version migration guide\n └── ADVANCED.md # Advanced usage\n```\n\n### API Reference Format\n\n```markdown\n# API Reference\n\n## Types\n\n### TypeName\n\nDescription of what this type represents and when to use it.\n\n**Definition:**\n```go\ntype TypeName struct {\n Field1 string // Description of field\n Field2 int // Description of field\n}\n```\n\n**Example:**\n```go\nt := TypeName{\n Field1: \"value\",\n Field2: 42,\n}\n```\n\n## Functions\n\n### FunctionName\n\nDescription of what this function does.\n\n**Signature:**\n```go\nfunc FunctionName(param1 Type1, param2 Type2) (ReturnType, error)\n```\n\n**Parameters:**\n| Name | Type | Description |\n|------|------|-------------|\n| param1 | Type1 | What this parameter controls |\n| param2 | Type2 | What this parameter controls |\n\n**Returns:**\n| Type | Description |\n|------|-------------|\n| ReturnType | What is returned |\n| error | When errors occur |\n\n**Example:**\n```go\nresult, err := FunctionName(\"value\", 42)\nif err != nil {\n log.Fatal(err)\n}\n```\n\n**See Also:** [RelatedFunction](#relatedfunction), [RelatedType](#typename)\n```\n\n### Documentation Completeness Checklist\n\n- [ ] All public types documented\n- [ ] All public functions/methods documented\n- [ ] All parameters explained\n- [ ] Return values explained\n- [ ] At least one example per function\n- [ ] Cross-references to related items\n- [ ] Error conditions documented\n\n## 3. Migration Guides\n\n### Structure\n\n```markdown\n# Migration Guide: vX.Y to vX.Z\n\n## Overview\n\nBrief summary of what changed and why.\n\n## Breaking Changes\n\n### Change 1: [Description]\n\n**Before (vX.Y):**\n```go\n// Old way\nclient.OldMethod()\n```\n\n**After (vX.Z):**\n```go\n// New way\nclient.NewMethod()\n```\n\n**Migration steps:**\n1. Find all calls to `OldMethod()`\n2. Replace with `NewMethod()`\n3. Update parameters as needed\n\n### Change 2: [Description]\n...\n\n## Deprecations\n\n| Deprecated | Replacement | Removal Version |\n|------------|-------------|-----------------|\n| `OldFunc()` | `NewFunc()` | v2.0.0 |\n\n## New Features\n\n### Feature 1\nDescription and example.\n\n## Behavioral Changes\n\nChanges that don't require code modifications but affect runtime behavior.\n\n| Behavior | Before | After |\n|----------|--------|-------|\n| Default timeout | 30s | 60s |\n```\n\n### Migration Guide Checklist\n\n- [ ] All breaking changes listed with before/after examples\n- [ ] Step-by-step migration instructions\n- [ ] Deprecation warnings with replacement suggestions\n- [ ] Behavioral changes documented (even if not breaking)\n- [ ] New features highlighted\n\n## 4. Changelog Practices\n\n### Conventional Commits\n\nUse conventional commit format for automated changelog generation:\n\n```\n\u003ctype>(\u003cscope>): \u003cdescription>\n\n[optional body]\n\n[optional footer(s)]\n```\n\n**Types:**\n| Type | Description | Changelog Section |\n|------|-------------|-------------------|\n| `feat` | New feature | Added |\n| `fix` | Bug fix | Fixed |\n| `docs` | Documentation only | Documentation |\n| `style` | Formatting, no code change | (usually omitted) |\n| `refactor` | Code change, no feature/fix | Changed |\n| `perf` | Performance improvement | Performance |\n| `test` | Adding tests | (usually omitted) |\n| `chore` | Maintenance tasks | (usually omitted) |\n\n**Examples:**\n```\nfeat(parser): add support for year field in cron expressions\n\nfix(scheduler): prevent duplicate job execution on restart\n\nFixes #123\n\ndocs(readme): update installation instructions\n\nBREAKING CHANGE: The `OldMethod` has been removed. Use `NewMethod` instead.\n```\n\n### CHANGELOG.md Format\n\n```markdown\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/),\nand this project adheres to [Semantic Versioning](https://semver.org/).\n\n## [Unreleased]\n\n### Added\n- New feature description (#PR)\n\n### Fixed\n- Bug fix description (#PR)\n\n## [1.2.0] - 2026-01-15\n\n### Added\n- Feature A for doing X (#101)\n- Feature B for doing Y (#102)\n\n### Changed\n- Improved performance of Z by 15% (#103)\n\n### Deprecated\n- `OldMethod()` - use `NewMethod()` instead\n\n### Fixed\n- Fixed crash when input is empty (#104)\n\n### Security\n- Updated dependency X to fix CVE-YYYY-NNNN (#105)\n\n## [1.1.0] - 2026-01-01\n...\n\n[Unreleased]: https://github.com/owner/repo/compare/v1.2.0...HEAD\n[1.2.0]: https://github.com/owner/repo/compare/v1.1.0...v1.2.0\n[1.1.0]: https://github.com/owner/repo/releases/tag/v1.1.0\n```\n\n### Changelog Quality Checklist\n\n- [ ] Every release has an entry\n- [ ] Changes categorized correctly (Added, Changed, Fixed, etc.)\n- [ ] PR/Issue numbers referenced\n- [ ] Breaking changes prominently marked\n- [ ] Security fixes highlighted\n- [ ] Links to compare views at bottom\n\n## 5. Code Example Quality\n\n### Requirements for Code Examples\n\n1. **Complete** - Include all necessary imports\n2. **Runnable** - Can be copy-pasted and executed\n3. **Correct** - Actually works as described\n4. **Error-handled** - Show proper error handling\n5. **Idiomatic** - Follow language conventions\n\n**Bad Example:**\n```go\n// Missing imports, no error handling\nc := cron.New()\nc.AddFunc(\"* * * * *\", myJob)\nc.Start()\n```\n\n**Good Example:**\n```go\npackage main\n\nimport (\n \"log\"\n \"time\"\n\n cron \"github.com/example/cron\"\n)\n\nfunc main() {\n c := cron.New()\n\n _, err := c.AddFunc(\"* * * * *\", func() {\n log.Println(\"Job executed at\", time.Now())\n })\n if err != nil {\n log.Fatal(\"Failed to add job:\", err)\n }\n\n c.Start()\n defer c.Stop()\n\n // Keep running\n select {}\n}\n```\n\n## Related References\n\n- `references/code-review.md` - Code review checklist (includes doc accuracy)\n- `references/quick-start-guide.md` - Getting started patterns\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7832,"content_sha256":"32bc63660058604d1947bf4890b8b891bb02abfb27abf9e92e637b9ad430e4ba"},{"filename":"references/dynamic-analysis.md","content":"# Dynamic Analysis Guide\n\n> OpenSSF Badge Criteria: `dynamic_analysis`, `dynamic_analysis_unsafe`, `dynamic_analysis_enable_assertions`\n\n## Overview\n\nDynamic analysis examines program behavior during execution, catching issues that static\nanalysis cannot detect. This guide covers implementation for various languages.\n\n## Go Dynamic Analysis\n\n### Race Detection\n\nThe Go race detector finds data races at runtime:\n\n```yaml\n# .github/workflows/ci.yml\n- name: Race detection\n run: go test -race ./...\n```\n\n**Local usage:**\n```bash\n# Run tests with race detector\ngo test -race ./...\n\n# Run application with race detector\ngo run -race main.go\n\n# Build with race detector (for testing only, not production)\ngo build -race -o app-race .\n```\n\n**Common race patterns detected:**\n- Unsynchronized map access\n- Concurrent slice modification\n- Shared variable access without mutex\n- Channel misuse\n\n### Fuzz Testing\n\nGo 1.18+ includes native fuzzing:\n\n```go\n// fuzz_test.go\nfunc FuzzParseInput(f *testing.F) {\n // Seed corpus\n f.Add(\"valid input\")\n f.Add(\"\")\n f.Add(\"special!@#$%\")\n\n f.Fuzz(func(t *testing.T, input string) {\n // Function should not panic\n result, err := ParseInput(input)\n if err != nil {\n return // Errors are acceptable\n }\n // Verify invariants\n if result.Length != len(input) {\n t.Errorf(\"length mismatch\")\n }\n })\n}\n```\n\n**Running fuzz tests:**\n```bash\n# Run for 60 seconds\ngo test -fuzz=FuzzParseInput -fuzztime=60s ./...\n\n# Run until failure\ngo test -fuzz=FuzzParseInput ./...\n\n# Run with specific seed corpus\ngo test -fuzz=FuzzParseInput -fuzzdir=testdata/fuzz ./...\n```\n\n**CI integration:**\n```yaml\n- name: Fuzz tests\n run: |\n go test -fuzz=. -fuzztime=2m ./...\n```\n\n### Memory Sanitizer (CGO)\n\nFor CGO code, use memory sanitizers:\n\n```bash\n# Address sanitizer (ASan)\nCC=clang CGO_ENABLED=1 go test -msan ./...\n\n# Requires clang and libmsan\n```\n\n### Integration Testing with Assertions\n\nEnable runtime assertions in tests:\n\n```go\n// main.go\nvar assertionsEnabled = false\n\nfunc assert(condition bool, msg string) {\n if assertionsEnabled && !condition {\n panic(\"assertion failed: \" + msg)\n }\n}\n\n// main_test.go\nfunc TestMain(m *testing.M) {\n assertionsEnabled = true\n os.Exit(m.Run())\n}\n```\n\n---\n\n## Python Dynamic Analysis\n\n### Fuzz Testing with Atheris\n\n```python\n# fuzz_test.py\nimport atheris\nimport sys\n\ndef TestOneInput(data):\n fdp = atheris.FuzzedDataProvider(data)\n try:\n my_function(fdp.ConsumeString(100))\n except ValueError:\n pass # Expected errors\n\natheris.Setup(sys.argv, TestOneInput)\natheris.Fuzz()\n```\n\n**CI integration:**\n```yaml\n- name: Fuzz tests\n run: |\n pip install atheris\n timeout 120 python fuzz_test.py || true\n```\n\n### Memory Profiling\n\n```python\n# Use memory_profiler\nfrom memory_profiler import profile\n\n@profile\ndef my_function():\n # Function code\n pass\n```\n\n### pytest Assertions\n\n```python\n# conftest.py\nimport pytest\n\ndef pytest_configure(config):\n # Enable assertions in optimized mode\n import builtins\n builtins.__debug__ = True\n```\n\n---\n\n## Rust Dynamic Analysis\n\n### Miri (Undefined Behavior Detection)\n\n```bash\n# Install Miri\nrustup +nightly component add miri\n\n# Run tests under Miri\ncargo +nightly miri test\n```\n\n### Sanitizers\n\n```bash\n# Address sanitizer\nRUSTFLAGS=\"-Z sanitizer=address\" cargo +nightly test\n\n# Thread sanitizer\nRUSTFLAGS=\"-Z sanitizer=thread\" cargo +nightly test\n\n# Memory sanitizer\nRUSTFLAGS=\"-Z sanitizer=memory\" cargo +nightly test\n```\n\n### cargo-fuzz\n\n```bash\n# Install\ncargo install cargo-fuzz\n\n# Create fuzz target\ncargo fuzz init\ncargo fuzz add my_target\n\n# Run\ncargo +nightly fuzz run my_target\n```\n\n---\n\n## PHP Dynamic Analysis\n\n### Fuzz Testing with nikic/php-fuzzer\n\nCoverage-guided fuzzer for PHP library and parser testing:\n\n**Installation:**\n```bash\ncomposer require --dev nikic/php-fuzzer:^0.0.11\n```\n\n**Creating a fuzz target:**\n```php\n\u003c?php\n// Tests/Fuzz/MyParserTarget.php\ndeclare(strict_types=1);\n\nuse MyVendor\\MyPackage\\Parser;\n\nrequire_once __DIR__ . '/../../vendor/autoload.php';\n\n/** @var PhpFuzzer\\Config $config */\n$parser = new Parser();\n\n$config->setTarget(function (string $input) use ($parser): void {\n // Function should not crash on any input\n $parser->parse($input);\n});\n\n// Prevent memory exhaustion\n$config->setMaxLen(65536);\n```\n\n**Running fuzz tests:**\n```bash\n# Create seed corpus directory\nmkdir -p Tests/Fuzz/corpus/my-parser\n\n# Add seed inputs (valid and edge-case inputs)\necho '\u003cvalid>input\u003c/valid>' > Tests/Fuzz/corpus/my-parser/valid.txt\necho '' > Tests/Fuzz/corpus/my-parser/empty.txt\n\n# Run fuzzer (corpus is positional argument, not --corpus option)\n.Build/bin/php-fuzzer fuzz Tests/Fuzz/MyParserTarget.php \\\n Tests/Fuzz/corpus/my-parser \\\n --max-runs 10000\n```\n\n**CI integration (optional - weekly schedule):**\n```yaml\n- name: Fuzz tests\n run: |\n composer ci:fuzz\n continue-on-error: true # Don't block on fuzz findings\n```\n\n### Mutation Testing with Infection\n\nVerifies test suite quality by introducing code mutations:\n\n**Installation:**\n```bash\ncomposer require --dev infection/infection:^0.27\n```\n\n**Configuration (infection.json5):**\n```json5\n{\n \"$schema\": \"https://raw.githubusercontent.com/infection/infection/master/resources/schema.json\",\n \"source\": {\n \"directories\": [\"src\"]\n },\n \"minMsi\": 60,\n \"minCoveredMsi\": 80,\n \"testFramework\": \"phpunit\"\n}\n```\n\n**Running mutation tests:**\n```bash\n# Run with coverage from PHPUnit\n./vendor/bin/phpunit --coverage-xml=build/coverage-xml\n./vendor/bin/infection --threads=4 --coverage=build/coverage-xml\n\n# Quick run (only covered code)\n./vendor/bin/infection --threads=4 --only-covered\n```\n\n**CI integration:**\n```yaml\n- name: Mutation tests\n run: |\n ./vendor/bin/infection \\\n --threads=4 \\\n --min-msi=60 \\\n --min-covered-msi=80 \\\n --only-covered\n```\n\n### Memory and Error Detection\n\n```bash\n# Run with memory limit validation\nphp -d memory_limit=256M vendor/bin/phpunit\n\n# Enable assertions (php.ini or runtime)\nphp -d zend.assertions=1 -d assert.exception=1 vendor/bin/phpunit\n```\n\n---\n\n## JavaScript/TypeScript Dynamic Analysis\n\n### Jest with Coverage\n\n```javascript\n// jest.config.js\nmodule.exports = {\n collectCoverage: true,\n coverageThreshold: {\n global: {\n branches: 80,\n functions: 80,\n lines: 80,\n },\n },\n};\n```\n\n### Fuzzing with jsfuzz\n\n```javascript\n// fuzz.js\nconst jsfuzz = require('jsfuzz');\n\nfunction fuzz(data) {\n const str = data.toString();\n myFunction(str);\n}\n\nmodule.exports = { fuzz };\n```\n\n---\n\n## CI/CD Integration\n\n### Comprehensive Dynamic Analysis Workflow\n\n```yaml\n# .github/workflows/dynamic-analysis.yml\nname: Dynamic Analysis\n\non:\n push:\n branches: [main]\n pull_request:\n schedule:\n - cron: '0 0 * * 0' # Weekly\n\njobs:\n race-detection:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-go@v5\n with:\n go-version-file: go.mod\n - name: Race detector\n run: go test -race -v ./...\n\n fuzz-tests:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-go@v5\n with:\n go-version-file: go.mod\n - name: Fuzz tests\n run: |\n # Run each fuzz test for 1 minute\n for fuzz in $(go test -list 'Fuzz.*' ./... 2>/dev/null | grep Fuzz); do\n go test -fuzz=\"$fuzz\" -fuzztime=1m ./... || true\n done\n\n integration-tests:\n runs-on: ubuntu-latest\n services:\n postgres:\n image: postgres:15\n env:\n POSTGRES_PASSWORD: test\n options: >-\n --health-cmd pg_isready\n --health-interval 10s\n --health-timeout 5s\n --health-retries 5\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-go@v5\n with:\n go-version-file: go.mod\n - name: Integration tests\n run: go test -v -tags=integration ./...\n env:\n DATABASE_URL: postgres://postgres:test@localhost/test\n```\n\n---\n\n## OSS-Fuzz Integration\n\nFor long-term fuzzing, consider [OSS-Fuzz](https://google.github.io/oss-fuzz/):\n\n1. Create `project.yaml`:\n```yaml\nhomepage: \"https://github.com/org/repo\"\nlanguage: go\nprimary_contact: \"[email protected]\"\n```\n\n2. Create `Dockerfile`:\n```dockerfile\nFROM gcr.io/oss-fuzz-base/base-builder-go\n\nRUN git clone --depth 1 https://github.com/org/repo /src/repo\nWORKDIR /src/repo\nCOPY build.sh /src/\n```\n\n3. Create `build.sh`:\n```bash\n#!/bin/bash\ncompile_native_go_fuzzer github.com/org/repo/pkg FuzzFunction fuzz_function\n```\n\n---\n\n## Metrics and Reporting\n\n### Coverage with Dynamic Analysis\n\n```yaml\n- name: Generate coverage report\n run: |\n go test -race -coverprofile=coverage.out -covermode=atomic ./...\n go tool cover -html=coverage.out -o coverage.html\n\n- name: Upload coverage\n uses: codecov/codecov-action@v4\n with:\n files: coverage.out\n flags: dynamic-analysis\n```\n\n### Tracking Fuzz Coverage\n\n```bash\n# Generate fuzz coverage\ngo test -fuzz=Fuzz -fuzztime=5m -coverprofile=fuzz.out ./...\ngo tool cover -func=fuzz.out\n```\n\n---\n\n## Badge Criteria Verification\n\n### `dynamic_analysis` (Met if any of these are true)\n- [ ] Fuzz tests exist and run in CI\n- [ ] Race detector runs in CI\n- [ ] Memory sanitizers used (if CGO)\n- [ ] Integration tests with real dependencies\n\n### `dynamic_analysis_unsafe` (For memory-unsafe languages)\n- [ ] Address sanitizer enabled\n- [ ] Memory sanitizer enabled\n- [ ] Undefined behavior sanitizer enabled\n\n### `dynamic_analysis_enable_assertions` (Met if)\n- [ ] Tests run without `-O` optimization flag\n- [ ] Debug assertions enabled in test builds\n- [ ] Panic on assertion failure in tests\n\n---\n\n## Resources\n\n- [Go Fuzz Testing](https://go.dev/doc/security/fuzz/)\n- [Go Race Detector](https://go.dev/doc/articles/race_detector)\n- [OSS-Fuzz](https://google.github.io/oss-fuzz/)\n- [AFL++ Fuzzer](https://github.com/AFLplusplus/AFLplusplus)\n- [Atheris Python Fuzzer](https://github.com/google/atheris)\n- [nikic/php-fuzzer](https://github.com/nikic/PHP-Fuzzer) - PHP coverage-guided fuzzer\n- [Infection PHP](https://infection.github.io/) - PHP mutation testing\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10248,"content_sha256":"512d9a7f4e7fdee030560a73471b5401214fc5d9c210a81666c9b2c610851c58"},{"filename":"references/general.md","content":"# General Enterprise Readiness Checks\n\nUniversal checks applicable to any platform and language. Aligned with OpenSSF Scorecard,\nBest Practices Badge, and S2C2F frameworks.\n\n## Governance & Policy (10 points)\n\n### Security Policy (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| SECURITY.md exists with contact info | 2 | Check for `SECURITY.md` in repository root |\n| Vulnerability disclosure process defined | 1 | Verify clear reporting instructions |\n\n**Implementation**: Create `SECURITY.md` with:\n- Security contact (email or form)\n- Vulnerability reporting process\n- Expected response timeline\n- Scope of security policy\n\n### License Compliance (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| LICENSE file present in repository root | 2 | Check for `LICENSE`, `LICENSE.md`, or `COPYING` |\n| OSI-approved or FSF-approved license | 1 | Verify license type |\n\n### Vulnerability Response SLA (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Initial response within 14 days | 1 | Check SECURITY.md for SLA commitment |\n| Critical vulnerabilities fixed within 30 days | 1 | Check vulnerability history or policy |\n\n**Best Practice**: Define SLAs by severity:\n- Critical: Fix within 7 days\n- High: Fix within 30 days\n- Medium: Fix within 90 days\n- Low: Fix within 180 days\n\n### Project Maintenance (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Active commits within last 90 days | 1 | `git log --since=\"90 days ago\" --oneline | head -5` |\n| Issues/PRs responded to | 1 | Check recent issue activity |\n\n---\n\n## Supply Chain Security (15 points)\n\n### Binary Artifacts (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| No generated binaries in source | 2 | `git ls-files \\| grep -E \"\\.(exe\\|dll\\|so\\|dylib\\|bin\\|o\\|a)$\"` should return empty |\n| No compiled assets in repository | 1 | Check for minified bundles, compiled bytecode |\n\n**Why**: Binary artifacts in source repositories cannot be reviewed and may contain malware.\n\n### Provenance Attestation (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| SLSA Level 1+ build provenance exists | 2 | Look for `.intoto.jsonl` files or build attestations |\n| Provenance automatically generated in CI | 1 | Check CI/CD for provenance generation steps |\n| SLSA Level 3 achieved | 1 | Verify isolated build environment, signed provenance |\n\n**Implementation**: Use platform-specific SLSA builders or manually generate with in-toto.\n\n### Artifact Signing (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Release artifacts are signed | 2 | Look for `.sig`, `.asc`, or `.pem` files |\n| Keyless signing with OIDC (preferred) | 2 | Check for Sigstore/Cosign with workload identity |\n\n**Implementation**: Prefer keyless signing (Cosign + OIDC) over traditional GPG keys.\n\n### Software Bill of Materials (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| SBOM generated for releases | 1 | Look for `.sbom.json`, `.spdx.json`, or CycloneDX files |\n| SBOM in standard format (SPDX/CycloneDX) | 1 | Verify format compliance |\n\n**Tools**: Syft, Trivy, or language-specific SBOM generators.\n\n### Checksums (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| SHA256 checksums for all artifacts | 1 | Look for `checksums.txt` or `SHA256SUMS` |\n| Checksums are signed | 1 | Look for `.sig` or `.pem` accompanying checksums |\n\n---\n\n## Dependency Consumption (10 points)\n\n*Aligned with S2C2F (Secure Supply Chain Consumption Framework)*\n\n### Dependency Ingestion (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Dependencies from trusted registries only | 1 | Check package manager config (npm, Go proxy, PyPI) |\n| Lockfile committed (go.sum, package-lock.json) | 1 | Verify lockfile exists and is committed |\n| Dependency update automation enabled | 1 | Check for Dependabot, Renovate, or similar |\n\n### Vulnerability Scanning (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Dependency vulnerability scanning in CI | 2 | Check for govulncheck, npm audit, Snyk, etc. |\n| Blocking on high/critical vulnerabilities | 1 | Verify CI fails on security issues |\n| Malware detection enabled | 1 | Check for Socket.dev, Phylum, or similar |\n\n**Tools**: govulncheck (Go), npm audit (Node), pip-audit (Python), Snyk, Socket.dev\n\n### License Auditing (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Dependency license scanning configured | 2 | Check for go-licenses, FOSSA, Snyk, license-checker |\n| License policy defined (allow/deny lists) | 1 | Check for license policy configuration |\n\n**Tools**: FOSSA, Snyk, license-checker, go-licenses\n\n---\n\n## Quality Gates (10 points)\n\n### Code Coverage (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Coverage measurement exists | 1 | Check CI for coverage reports |\n| Coverage threshold enforced (60%+) | 1 | Check for threshold enforcement in CI |\n| Coverage trend tracking | 1 | Check for Codecov, Coveralls, or similar |\n\n### Static Analysis (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Multiple linters configured | 2 | Check for linter config files |\n| Linting enforced in CI (fails on issues) | 1 | Check CI for lint steps |\n| SAST tool configured (CodeQL, Semgrep) | 1 | Check for security-focused static analysis |\n\n### Secret Scanning (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Pre-commit secret scanning | 2 | Check for gitleaks, trufflehog, or similar |\n| CI secret scanning | 1 | Check CI for secret detection steps |\n\n**Tools**: gitleaks, trufflehog, detect-secrets, git-secrets\n\n---\n\n## Testing Layers (10 points)\n\n### Unit Tests (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Unit tests exist | 1 | Check for test files |\n| Unit tests run in CI | 1 | Check CI configuration |\n| Tests accompany new features (policy) | 1 | Check CONTRIBUTING.md or PR template |\n\n### Integration Tests (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Integration tests exist | 1 | Check for integration test directory/tags |\n| Integration tests run in CI | 1 | Check CI configuration |\n| Database/API mocking or fixtures | 1 | Check test infrastructure |\n\n### Security Testing (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Fuzz tests exist | 1 | Check for fuzz test files |\n| Fuzz testing in CI (or OSS-Fuzz) | 1 | Check CI or OSS-Fuzz enrollment |\n| Dynamic analysis (DAST) | 1 | Check for runtime security testing |\n| Penetration testing documented | 1 | Check security documentation |\n\n---\n\n## Documentation & Governance (5 points)\n\n### Project Documentation (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| README with project description | 1 | Check README.md exists and is informative |\n| CONTRIBUTING.md with guidelines | 1 | Check for contribution guidelines |\n| API/reference documentation | 1 | Check for docs/ directory or generated docs |\n\n### Community Health (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| CODEOWNERS file configured | 1 | Check `.github/CODEOWNERS` or `CODEOWNERS` |\n| Issue/PR templates exist | 1 | Check `.github/ISSUE_TEMPLATE/` and `.github/PULL_REQUEST_TEMPLATE.md` |\n\n---\n\n## Total: 60 points\n\n**Scoring Thresholds**:\n- 54-60: Excellent universal security posture\n- 42-53: Good, minor improvements needed\n- 30-41: Fair, significant gaps\n- Below 30: Poor, major improvements required\n\n---\n\n## OpenSSF Alignment Reference\n\n| OpenSSF Scorecard Check | This Skill Section | Status |\n|------------------------|-------------------|--------|\n| Binary-Artifacts | Supply Chain Security | ✅ Covered |\n| CII-Best-Practices | Governance & Policy | ✅ Covered (via compliance) |\n| Contributors | Documentation & Governance | ⚠️ Partial |\n| Fuzzing | Testing Layers | ✅ Covered |\n| License | Governance & Policy | ✅ Covered |\n| Maintained | Governance & Policy | ✅ Covered |\n| SBOM | Supply Chain Security | ✅ Covered |\n| SAST | Quality Gates | ✅ Covered |\n| Security-Policy | Governance & Policy | ✅ Covered |\n| Signed-Releases | Supply Chain Security | ✅ Covered |\n| Vulnerabilities | Dependency Consumption | ✅ Covered |\n\n| S2C2F Practice | This Skill Section | Status |\n|----------------|-------------------|--------|\n| Ingest It | Dependency Consumption | ✅ Covered |\n| Scan It | Quality Gates, Dependency Consumption | ✅ Covered |\n| Inventory It | Supply Chain Security (SBOM) | ✅ Covered |\n| Update It | Dependency Consumption | ✅ Covered |\n| Audit It | Governance & Policy | ✅ Covered |\n| Enforce It | Quality Gates | ✅ Covered |\n| Rebuild It | (Platform-specific) | ⚠️ Partial |\n| Fix It + Upstream | (Out of scope) | ❌ N/A |\n\n| OSPS Baseline Category | This Skill Section | Status |\n|------------------------|-------------------|--------|\n| Access Control (AC) | Governance & Policy, Supply Chain | ✅ Covered |\n| Build & Release (BR) | Supply Chain Security | ✅ Covered |\n| Documentation (DO) | Documentation & Governance | ✅ Covered |\n| Governance (GV) | Governance & Policy | ✅ Covered |\n| Legal (LE) | Governance & Policy (License) | ✅ Covered |\n| Quality Assurance (QA) | Quality Gates, Testing | ✅ Covered |\n| Vulnerability Mgmt (VM) | Governance & Policy, Dependency Consumption | ✅ Covered |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9718,"content_sha256":"0377f4ba875626501e21962ebb1850a838896d3d74cdcb8eff00c136c22cc95f"},{"filename":"references/github.md","content":"# GitHub-Specific Enterprise Readiness Checks\n\nChecks specific to GitHub-hosted repositories and GitHub Actions.\nAligned with OpenSSF Scorecard high-risk checks.\n\n## Dangerous Workflow Patterns (8 points) - CRITICAL\n\n*This section addresses the OpenSSF Scorecard \"Dangerous-Workflow\" check (Critical risk)*\n\n### Script Injection Prevention (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| No direct interpolation of untrusted input in `run` blocks | 2 | Grep for `\\$\\{\\{.*github\\.event` in workflow files |\n| Environment variables used for user input | 1 | Check for `env:` block before `run:` |\n| No `pull_request_target` with checkout of PR code | 1 | Check workflow triggers and checkout patterns |\n\n**Critical Pattern - NEVER DO THIS:**\n```yaml\n# DANGEROUS - Script injection vulnerability\n- run: echo \"Title: ${{ github.event.issue.title }}\"\n```\n\n**Safe Pattern - ALWAYS DO THIS:**\n```yaml\n# SAFE - Use environment variables\n- name: Process issue\n env:\n TITLE: ${{ github.event.issue.title }}\n run: echo \"Title: $TITLE\"\n```\n\n### Dangerous Triggers (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| No `pull_request_target` without careful review | 2 | `grep -r \"pull_request_target\" .github/workflows/` |\n| No `workflow_run` with untrusted artifact access | 1 | Check for artifact downloads from forked PRs |\n| Checkout uses explicit ref for PR workflows | 1 | Verify `ref: ${{ github.event.pull_request.head.sha }}` pattern |\n\n**Why**: `pull_request_target` runs in the context of the base branch with write permissions,\nmaking it dangerous when combined with checkout of PR code from forks.\n\n---\n\n## Code Review Requirements (7 points)\n\n*This section addresses the OpenSSF Scorecard \"Code-Review\" check (High risk)*\n\n### Branch Protection (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Default branch protected | 1 | Check repository settings or `gh api repos/{owner}/{repo}/branches/{branch}/protection` |\n| Require pull request before merging | 1 | Check branch protection rules |\n| Dismiss stale reviews on new commits | 1 | Check branch protection settings |\n\n### Review Enforcement (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Require at least 1 approving review | 2 | Check required reviewers setting |\n| Code owners review required | 1 | Check `require_code_owner_reviews` setting |\n| Require conversation resolution before merge | 1 | Check `required_conversation_resolution` setting |\n\n**Why conversation resolution matters**: Without this setting, review feedback can be silently ignored — a PR with unresolved threads can be merged, defeating the purpose of code review. This is especially important when automated reviewers (e.g., GitHub Copilot) leave actionable comments.\n\n**Implementation**:\n```bash\n# Check branch protection via GitHub CLI\ngh api repos/{owner}/{repo}/branches/main/protection \\\n --jq '{\n enforce_admins: .enforce_admins.enabled,\n required_reviews: .required_pull_request_reviews.required_approving_review_count,\n dismiss_stale: .required_pull_request_reviews.dismiss_stale_reviews,\n conversation_resolution: .required_conversation_resolution.enabled\n }'\n\n# Quick check: is conversation resolution enabled?\ngh api repos/{owner}/{repo}/branches/main/protection \\\n --jq 'if .required_conversation_resolution.enabled then \"✅ Conversation resolution required\" else \"❌ NOT required - enable it\" end'\n```\n\n**Enable conversation resolution**:\n```bash\n# WARNING: This PUT request replaces all existing branch protection settings.\n# You must include all settings you want to keep in the JSON payload below.\ngh api repos/{owner}/{repo}/branches/main/protection \\\n | jq 'del(.required_conversation_resolution) | . + {\"required_conversation_resolution\": true}' \\\n | gh api repos/{owner}/{repo}/branches/main/protection -X PUT --input -\n```\n\nOr via GitHub UI: Settings → Branches → Edit → Check **\"Require conversation resolution before merging\"**\n\n---\n\n## Workflow Hardening (10 points)\n\n### Harden Runner (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| step-security/harden-runner on security-sensitive jobs | 2 | Check workflow job steps |\n| Egress policy configured | 1 | Check for `egress-policy: audit` or `block` |\n\n**Implementation**:\n```yaml\n- name: Harden Runner\n uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0\n with:\n egress-policy: audit\n```\n\n### Pinned Action Versions (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| All actions pinned to SHA (not tags) | 3 | `grep -E \"uses:.*@[a-f0-9]{40}\" .github/workflows/*.yml` |\n| Version comments for readability | 1 | Check for `# vX.Y.Z` comments |\n\n**Example**:\n```yaml\n# Good - Pinned to SHA with version comment\nuses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n\n# Bad - Tag can be moved by attacker\nuses: actions/checkout@v4\n```\n\n### Minimal Permissions (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Top-level `permissions: {}` (deny-all) on every workflow | 2 | Check workflow top-level permissions |\n| Per-job permission escalation only | 1 | Check job-level permissions are minimal |\n\n**Implementation** (proven pattern — scores 10/10 on Token-Permissions):\n```yaml\n# BEST: deny-all at top, grant per-job (scores 10/10)\npermissions: {}\n\njobs:\n build:\n permissions:\n contents: write # Only what's needed for this job\n```\n\n**Why `permissions: {}` not `read-all`**: `read-all` still grants read access to all scopes (actions, checks, contents, deployments, issues, packages, etc.). The `{}` pattern denies everything by default, then each job declares only what it needs. Real-world comparison across 6 Netresearch TYPO3 extensions shows repos using `permissions: {}` score 10/10 on Token-Permissions while those using `read-all` or no top-level permissions score 0/10.\n\n**Apply to ALL workflows**, not just `ci.yml`:\n```bash\n# Check which workflows lack top-level permissions: {}\nfor f in .github/workflows/*.yml; do\n head -20 \"$f\" | grep -q \"permissions: {}\" || echo \"❌ $f missing permissions: {}\"\ndone\n```\n\n---\n\n## Security Features (10 points)\n\n### Dependabot (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Dependabot enabled | 1 | Check `.github/dependabot.yml` |\n| Grouped updates configured | 1 | Check for `groups:` in config |\n\n**Implementation**:\n```yaml\n# .github/dependabot.yml\nversion: 2\nupdates:\n - package-ecosystem: \"gomod\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n groups:\n all-dependencies:\n patterns: [\"*\"]\n```\n\n### CodeQL Analysis (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| CodeQL workflow exists | 1 | Check `.github/workflows/codeql*.yml` |\n| Scheduled scans (weekly minimum) | 1 | Check schedule trigger |\n| All relevant languages configured | 1 | Check language matrix |\n\n**Implementation**:\n```yaml\non:\n schedule:\n - cron: '0 6 * * 1' # Weekly on Monday\n```\n\n### Secret Scanning (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| GitHub secret scanning enabled | 1 | Check repository security settings |\n| Push protection enabled | 1 | Check secret scanning settings |\n\n### Merge Queue (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Merge queue enabled | 1 | Check branch protection settings |\n| Workflows handle `merge_group` event | 1 | Check workflow triggers |\n| Required status checks configured | 1 | Check branch protection settings |\n\n**Implementation**:\n```yaml\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n merge_group: # Required for merge queue\n```\n\n---\n\n## SLSA Implementation (6 points)\n\n### SLSA Level Achievement (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| SLSA Level 1 (provenance exists) | 1 | Check for `.intoto.jsonl` in releases |\n| SLSA Level 2 (hosted build, signed provenance) | 1 | Check for signed attestations |\n| SLSA Level 3 (isolated builder, unforgeable) | 2 | Check for slsa-github-generator usage |\n\n### Provenance Verification (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Verification instructions documented | 1 | Check README or release notes |\n| `slsa-verifier` compatible | 1 | Test with `slsa-verifier verify-artifact` |\n\n**Using slsa-github-generator (Go example)**:\n```yaml\nuses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]\nwith:\n go-version-file: go.mod\n config-file: .slsa-goreleaser/${{ matrix.target }}.yml\n evaluated-envs: \"VERSION:${{ github.ref_name }}, COMMIT:${{ github.sha }}\"\n upload-assets: true\n```\n\n**Critical**: Use `{{ .Env.VERSION }}` not `{{ .Tag }}` - SLSA builder uses different template syntax.\n\n---\n\n## Total: 41 points\n\n**Scoring Thresholds**:\n- 37-41: Excellent GitHub security posture\n- 29-36: Good, minor improvements needed\n- 21-28: Fair, significant gaps\n- Below 21: Poor, major improvements required\n\n---\n\n## OpenSSF Scorecard Alignment\n\n| Scorecard Check | This Skill Section | Status |\n|-----------------|-------------------|--------|\n| Branch-Protection | Code Review Requirements | ✅ Covered |\n| Code-Review | Code Review Requirements | ✅ Covered |\n| Dangerous-Workflow | Dangerous Workflow Patterns | ✅ Covered |\n| Dependency-Update-Tool | Security Features (Dependabot) | ✅ Covered |\n| Pinned-Dependencies | Workflow Hardening | ✅ Covered |\n| SAST | Security Features (CodeQL) | ✅ Covered |\n| Token-Permissions | Workflow Hardening | ✅ Covered |\n| Webhooks | (Not yet covered) | ⚠️ Future |\n\n---\n\n## OpenSSF Scorecard Optimization Guide\n\nBased on real-world data from 6 Netresearch TYPO3 extensions (scores 6.3–7.3), these are the most impactful improvements ranked by effort-to-reward.\n\n### Quick Wins (high impact, low effort)\n\n| Check | 0→10 Fix | Effort |\n|-------|----------|--------|\n| **Token-Permissions** | Add `permissions: {}` to top of ALL workflows + per-job scoping | 30 min |\n| **Security-Policy** | Add `SECURITY.md` with vulnerability reporting instructions | 10 min |\n| **Dependency-Update-Tool** | Add `.github/dependabot.yml` | 5 min |\n| **License** | Add `LICENSE` file | 5 min |\n\n### Medium Effort (significant impact)\n\n| Check | Fix | Effort |\n|-------|-----|--------|\n| **Branch-Protection** | Enable: require PR, require review, dismiss stale, conversation resolution | 15 min |\n| **SAST** | Add `codeql.yml` with scheduled weekly scans + all relevant languages | 15 min |\n| **Pinned-Dependencies** | Pin all actions to SHA with `# vX.Y.Z` comments, use Dependabot for updates | 30 min |\n| **CII-Best-Practices** | Register at bestpractices.dev and answer questionnaire | 1-2 hrs |\n| **Code-Review** | Merge ALL changes via reviewed PRs (no direct pushes to main) | Ongoing |\n\n### Checks That Cannot Reach 10/10\n\n| Check | Why | Best Achievable |\n|-------|-----|-----------------|\n| **Fuzzing** | Scorecard only recognizes OSS-Fuzz or ClusterFuzzLite, not PHPUnit fuzz suites | 0 (even with fuzz tests) |\n| **Packaging** | Requires publishing to GitHub Packages (not Packagist/TER) | -1 for PHP projects |\n| **Signed-Releases** | Requires SLSA provenance with `slsa-github-generator` + signed tags | 2-5 (achievable) |\n| **Contributors** | Based on number of distinct contributors — cannot be forced | Varies |\n| **Maintained** | Based on recent commit activity — penalizes stable projects | Varies |\n\n### Token-Permissions: The Biggest Win\n\nThe difference between 0/10 and 10/10 on Token-Permissions is a single line at the top of each workflow. This check has the highest weight-to-effort ratio.\n\n**Pattern** (must be on ALL `.github/workflows/*.yml`):\n```yaml\nname: CI\n\non: [push, pull_request]\n\npermissions: {} # \u003c-- This line scores 10/10\n\njobs:\n build:\n runs-on: ubuntu-latest\n permissions:\n contents: read # Only what this job needs\n steps:\n - uses: actions/checkout@SHA # vX.Y.Z\n```\n\n**Common mistake**: Adding `permissions` to only `ci.yml` but forgetting `codeql.yml`, `scorecard.yml`, `dependency-review.yml`, and other workflows. Scorecard checks ALL workflow files.\n\n### Code-Review: Requires Process Discipline\n\nScorecard checks whether recent commits on the default branch came through reviewed PRs. Direct pushes to main score 0/10.\n\n**Requirements for 10/10:**\n- All changes merged via pull request\n- Each PR has at least one approving review\n- No direct commits to default branch (even for trivial changes)\n- Solo maintainers: use auto-approval workflow for self-PRs (see github-project-skill)\n\n### CII-Best-Practices: Manual Registration Required\n\nRegister at https://www.bestpractices.dev/en/projects/new and answer the questionnaire. Projects with CI, tests, SECURITY.md, and Dependabot already meet most Passing criteria.\n\n---\n\n## Common GitHub Workflow Issues\n\n### Issue: gh CLI fails with \"not a git repository\"\n**Cause**: Jobs using `gh release download` or `gh release edit` need repository context.\n**Fix**: Add `actions/checkout` step before using gh CLI.\n\n### Issue: YAML heredoc parsing errors\n**Cause**: Incorrect indentation in multi-line scripts.\n**Fix**: Use consistent indentation, prefer `|` or `>` block scalars.\n\n### Issue: Merge queue builds fail\n**Cause**: Workflow doesn't handle `merge_group` event.\n**Fix**: Add `merge_group:` to workflow triggers.\n\n### Issue: Fork PRs can't access secrets\n**Cause**: GitHub doesn't expose secrets to fork PRs for security.\n**Fix**: Use `pull_request_target` carefully or use OIDC for external services.\n\n### Issue: Actions pinned to SHA break updates\n**Fix**: Use Dependabot with `package-ecosystem: \"github-actions\"` to update SHA pins.\n\n---\n\n## CI Optimization Patterns\n\n### Disk Space Management\n\nGitHub Actions runners have limited disk space (~14GB free on ubuntu-latest). Heavy test operations can exhaust this, causing failures like:\n\n```\nError: Failed to install package: requires 498.2 MB free space, but only 421.8 MB available\n```\n\n**Solution 1: Free Disk Space (Quick Fix)**\n\nAdd this step early in your workflow to reclaim ~25GB:\n\n```yaml\n- name: Free disk space\n run: |\n sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc\n df -h\n```\n\n**What gets removed:**\n| Directory | Size | Contents |\n|-----------|------|----------|\n| `/usr/share/dotnet` | ~6GB | .NET SDK (not needed for most projects) |\n| `/usr/local/lib/android` | ~10GB | Android SDK |\n| `/opt/ghc` | ~8GB | Haskell compiler |\n\n**Solution 2: Test Variable Overrides (Best Practice)**\n\nFor tests that install heavy dependencies (Docker images, Flatpak apps, large packages), override variables in test configuration:\n\n```yaml\n# molecule/default/converge.yml for Ansible\nvars:\n # Override for CI - minimal set reduces disk usage\n heavy_packages: [] # Empty in CI, full list in defaults/main.yml\n\n# Or for selective testing:\nvars:\n test_mode: true\n packages_to_install:\n - small-package-1 # Test the mechanism with minimal resources\n```\n\n**Why this works:**\n- Production deployments use full defaults from `defaults/main.yml`\n- CI tests validate the mechanism works with minimal resources\n- Avoids downloading gigabytes of packages in every CI run\n\n### Test Execution Optimization\n\n**Run expensive tests conditionally:**\n\n```yaml\n- name: Run lightweight tests\n run: npm test\n\n- name: Run heavy E2E tests\n if: github.event_name == 'push' && github.ref == 'refs/heads/main'\n run: npm run test:e2e\n```\n\n**Use job matrix efficiently:**\n\n```yaml\nstrategy:\n matrix:\n include:\n # Full test on main only\n - os: ubuntu-latest\n full_test: true\n # Quick smoke test on PRs\n - os: ubuntu-latest\n full_test: false\n fail-fast: false\n```\n\n### Docker Layer Caching\n\nFor Docker-heavy CI (Molecule, container builds):\n\n```yaml\n- uses: docker/setup-buildx-action@v3\n- uses: docker/build-push-action@v6\n with:\n context: .\n cache-from: type=gha\n cache-to: type=gha,mode=max\n```\n\n### Common CI Failure Patterns\n\n| Pattern | Symptom | Fix |\n|---------|---------|-----|\n| Disk space exhaustion | \"No space left on device\" | Free disk space step |\n| Memory exhaustion | OOM killed | Reduce parallelism, add swap |\n| Timeout | Job exceeds 6h limit | Split into multiple jobs |\n| Rate limiting | API calls fail | Add retry logic, caching |\n| Transient API errors | 502/503/504 Bad Gateway | Retry with exponential backoff |\n\n---\n\n## API Resilience Patterns\n\nGitHub API can return transient errors (502, 503, 504) during high load. Scripts making API calls should include retry logic.\n\n### Retry Logic for Python Scripts\n\n```python\nimport time\nimport requests\n\ndef github_request(url: str, token: str, max_retries: int = 3) -> dict | list:\n \"\"\"Make authenticated GitHub API request with retry logic.\"\"\"\n headers = {\n \"Authorization\": f\"Bearer {token}\",\n \"Accept\": \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n }\n\n for attempt in range(max_retries):\n try:\n response = requests.get(url, headers=headers, timeout=30)\n\n # Retry on transient errors\n if response.status_code in (429, 502, 503, 504):\n retry_after = int(response.headers.get(\"Retry-After\", 2 ** attempt))\n print(f\"Retry {attempt + 1}/{max_retries}: {response.status_code}, waiting {retry_after}s\")\n time.sleep(retry_after)\n continue\n\n response.raise_for_status()\n return response.json()\n\n except requests.exceptions.RequestException as e:\n if attempt \u003c max_retries - 1:\n wait_time = 2 ** attempt\n print(f\"Retry {attempt + 1}/{max_retries}: {e}, waiting {wait_time}s\")\n time.sleep(wait_time)\n else:\n raise\n\n raise RuntimeError(f\"Max retries exceeded for {url}\")\n```\n\n### Retry Logic for Shell Scripts\n\n```bash\n#!/bin/bash\n# GitHub API with retry logic\n\ngithub_api_request() {\n local url=\"$1\"\n local max_retries=3\n local attempt=0\n\n while [ $attempt -lt $max_retries ]; do\n response=$(curl -s -w \"\\n%{http_code}\" \\\n -H \"Authorization: Bearer $GITHUB_TOKEN\" \\\n -H \"Accept: application/vnd.github+json\" \\\n \"$url\")\n\n http_code=$(echo \"$response\" | tail -n1)\n body=$(echo \"$response\" | sed '$d')\n\n case $http_code in\n 200|201|204)\n echo \"$body\"\n return 0\n ;;\n 429|502|503|504)\n attempt=$((attempt + 1))\n wait_time=$((2 ** attempt))\n echo \"Retry $attempt/$max_retries: HTTP $http_code, waiting ${wait_time}s\" >&2\n sleep $wait_time\n ;;\n *)\n echo \"Error: HTTP $http_code\" >&2\n echo \"$body\" >&2\n return 1\n ;;\n esac\n done\n\n echo \"Max retries exceeded for $url\" >&2\n return 1\n}\n```\n\n### Retry Logic for GitHub Actions\n\n```yaml\n- name: Call GitHub API with retry\n run: |\n for i in 1 2 3; do\n response=$(gh api repos/${{ github.repository }}/releases/latest 2>&1) && break\n echo \"Attempt $i failed, retrying in $((i * 2)) seconds...\"\n sleep $((i * 2))\n done\n echo \"$response\"\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n```\n\n### When to Add Retry Logic\n\n| Scenario | Retry Needed? | Reason |\n|----------|---------------|--------|\n| CI workflow API calls | Yes | Transient failures break builds |\n| Release automation | Yes | Critical operations should be resilient |\n| One-time scripts | Optional | Manual retry is acceptable |\n| User-facing CLI tools | Yes | Better UX than cryptic errors |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":19943,"content_sha256":"30fd18bd27fbf6a32d040a7872a7bff9dd9b147866c40f6d6bd71f67148c1c83"},{"filename":"references/go.md","content":"# Go-Specific Enterprise Readiness Checks\n\nChecks specific to Go projects and the Go ecosystem.\n\n## Build Reproducibility (10 points)\n\n### Static Binaries (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| CGO_ENABLED=0 for static builds | 2 | Check build scripts/CI |\n| Static linking verified | 1 | `ldd binary` shows \"not a dynamic executable\" |\n\n**Implementation**:\n```bash\nCGO_ENABLED=0 go build -o myapp .\n```\n\n### Reproducible Paths (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| -trimpath flag used | 2 | Check build flags |\n\n**Implementation**:\n```bash\ngo build -trimpath -o myapp .\n```\n\n### Binary Optimization (2 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| -ldflags '-s -w' for stripped binaries | 2 | Check build flags |\n\n**Implementation**:\n```bash\ngo build -ldflags '-s -w' -o myapp .\n```\n\n### Toolchain Pinning (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Explicit go version in go.mod | 2 | Check `go X.Y` directive |\n| Toolchain directive present | 1 | Check `toolchain goX.Y.Z` directive |\n\n**Implementation**:\n```go\n// go.mod\nmodule mymodule\n\ngo 1.22\n\ntoolchain go1.22.0\n```\n\n---\n\n## Quality Tooling (10 points)\n\n### golangci-lint (4 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| golangci-lint configured | 2 | Check `.golangci.yml` or `.golangci.yaml` |\n| 20+ linters enabled | 2 | Count enabled linters in config |\n\n**Recommended Linters**:\n```yaml\n# .golangci.yml\nlinters:\n enable:\n # Bugs\n - govet\n - staticcheck\n - errcheck\n - ineffassign\n - typecheck\n\n # Style\n - gofmt\n - goimports\n - revive\n - misspell\n\n # Complexity\n - gocyclo\n - gocognit\n - funlen\n\n # Security\n - gosec\n - gocritic\n\n # Performance\n - prealloc\n - bodyclose\n\n # Error handling\n - errorlint\n - wrapcheck\n\n # Unused\n - unused\n - deadcode\n - unparam\n```\n\n### Vulnerability Scanning (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| govulncheck in CI | 2 | Check CI for govulncheck step |\n| Vulnerability check on PRs | 1 | Check PR workflow |\n\n**Implementation**:\n```yaml\n- name: Install govulncheck\n run: go install golang.org/x/vuln/cmd/govulncheck@latest\n\n- name: Run govulncheck\n run: govulncheck ./...\n```\n\n### Race Detector (3 points)\n| Criteria | Points | How to Check |\n|----------|--------|--------------|\n| Tests run with -race flag | 2 | Check test commands in CI |\n| Race detection on all packages | 1 | Verify `./...` scope |\n\n**Implementation**:\n```bash\ngo test -race -v ./...\n```\n\n---\n\n## Testing Patterns (bonus, not scored)\n\n### Table-Driven Tests\n- [ ] Tests use table-driven pattern\n- [ ] Subtests with `t.Run()` for clarity\n\n**Example**:\n```go\nfunc TestAdd(t *testing.T) {\n tests := []struct {\n name string\n a, b int\n expected int\n }{\n {\"positive\", 1, 2, 3},\n {\"negative\", -1, -2, -3},\n {\"zero\", 0, 0, 0},\n }\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n got := Add(tt.a, tt.b)\n if got != tt.expected {\n t.Errorf(\"Add(%d, %d) = %d; want %d\", tt.a, tt.b, got, tt.expected)\n }\n })\n }\n}\n```\n\n### Fuzz Testing (Go 1.18+)\n- [ ] Fuzz tests exist for parsing/input handling\n- [ ] Fuzz tests integrated with CI or OSS-Fuzz\n\n**Example**:\n```go\nfunc FuzzParse(f *testing.F) {\n f.Add([]byte(\"example input\"))\n f.Fuzz(func(t *testing.T, data []byte) {\n Parse(data) // Should not panic\n })\n}\n```\n\n### Coverage with Atomic Mode\n- [ ] Coverage uses `-covermode=atomic` for race-safe counting\n- [ ] Coverage output in standard format for tools\n\n**Implementation**:\n```bash\ngo test -race -coverprofile=coverage.out -covermode=atomic ./...\ngo tool cover -func=coverage.out\n```\n\n---\n\n## Architecture Patterns (bonus, not scored)\n\n### Hexagonal Architecture\n- [ ] Clear separation: domain, ports, adapters\n- [ ] Interfaces define contracts (ports)\n- [ ] External dependencies injected (adapters)\n\n### Dependency Injection\n- [ ] Dependencies passed via constructors\n- [ ] No global state or singletons\n- [ ] Interfaces for testability\n\n**Example**:\n```go\ntype Service struct {\n repo Repository // Interface, not concrete type\n log Logger\n}\n\nfunc NewService(repo Repository, log Logger) *Service {\n return &Service{repo: repo, log: log}\n}\n```\n\n### Error Handling\n- [ ] Errors wrapped with context\n- [ ] Sentinel errors for expected conditions\n- [ ] Error types for programmatic handling\n\n**Example**:\n```go\nimport \"fmt\"\n\nfunc doSomething() error {\n if err := dependency(); err != nil {\n return fmt.Errorf(\"failed to do something: %w\", err)\n }\n return nil\n}\n```\n\n### Structured Logging\n- [ ] Structured logging (slog, zerolog, zap)\n- [ ] Log levels appropriately used\n- [ ] Contextual fields included\n\n---\n\n## Module Management (bonus, not scored)\n\n### go.mod Best Practices\n- [ ] Module path matches import path\n- [ ] Go version specified\n- [ ] Dependencies regularly updated\n- [ ] `go mod tidy` runs cleanly\n\n### Vendoring (optional)\n- [ ] If vendoring, `vendor/` is committed\n- [ ] `go mod vendor` runs cleanly\n\n---\n\n## Total: 20 points\n\n**Scoring Thresholds**:\n- 18-20: Excellent Go practices\n- 14-17: Good, minor improvements needed\n- 10-13: Fair, significant gaps\n- Below 10: Poor, major improvements required\n\n---\n\n## CI Workflow Checklist\n\nEssential security and quality workflows for production Go projects:\n\n| Workflow | Purpose | Template |\n|----------|---------|----------|\n| OpenSSF Scorecard | Security practices measurement | `assets/workflows/scorecard.yml` |\n| CodeQL | Semantic security analysis | `assets/workflows/codeql.yml` |\n| Dependency Review | CVE/license scanning on PRs | `assets/workflows/dependency-review.yml` |\n| SLSA Provenance | Build attestation (L3) | `assets/workflows/slsa-provenance.yml` |\n\n### Implementation Priority\n\n1. **Dependency Review** - Immediate PR protection against known vulnerabilities\n2. **CodeQL** - Catches security bugs before merge\n3. **Scorecard** - Measures and tracks security posture\n4. **SLSA** - Supply chain security for releases\n\n### Standard Makefile Interface\n\nEnterprise-ready workflows depend on consistent Makefile targets:\n\n```bash\nmake test # Run tests with race detection and coverage\nmake build # Build the application binary\nmake lint # Run golangci-lint\nmake fuzz # Run fuzz tests\nmake vuln-check # Run govulncheck\n```\n\nSee `go-development` skill for Makefile templates.\n\n---\n\n## Go-Specific Tools Reference\n\n| Tool | Purpose | Install |\n|------|---------|---------|\n| golangci-lint | Multi-linter aggregator | `go install github.com/golangci-lint/golangci-lint/cmd/golangci-lint@latest` |\n| govulncheck | Vulnerability scanner | `go install golang.org/x/vuln/cmd/govulncheck@latest` |\n| staticcheck | Advanced static analysis | `go install honnef.co/go/tools/cmd/staticcheck@latest` |\n| gofumpt | Stricter gofmt | `go install mvdan.cc/gofumpt@latest` |\n| go-licenses | License compliance | `go install github.com/google/go-licenses@latest` |\n| syft | SBOM generation | Binary from GitHub releases |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7296,"content_sha256":"039041ff7c0a72556a592112666c9c525111cf4cedf023edd3915b51eff6d69a"},{"filename":"references/harden-runner-guide.md","content":"# StepSecurity Harden-Runner Operational Guide\n\n> Harden-Runner provides runtime security for GitHub Actions workflows by monitoring\n> and optionally blocking outbound network connections from CI jobs.\n\n## Egress Policy Modes\n\n### Audit Mode (`egress-policy: audit`)\n\nLogs all outbound connections without blocking anything. Use for:\n\n- **Initial deployment** on existing workflows to discover required domains\n- **Diagnostics** when debugging CI failures related to network access\n- **Incident detection** — audit mode detected the trivy-action supply chain compromise\n by revealing unexpected outbound connections to attacker-controlled domains\n\n```yaml\n- name: Harden-Runner\n uses: step-security/harden-runner@\u003csha> # vX.Y.Z\n with:\n egress-policy: audit\n```\n\n### Block Mode (`egress-policy: block`)\n\nOnly allows connections to explicitly listed domains. All other outbound traffic is denied.\n\n- **Would have PREVENTED** the trivy-action exfiltration — the compromised action's\n unauthorized outbound connection would have been blocked\n- Requires a curated allowlist of domains the job legitimately needs\n\n```yaml\n- name: Harden-Runner\n uses: step-security/harden-runner@\u003csha> # vX.Y.Z\n with:\n egress-policy: block\n allowed-endpoints: >\n github.com:443\n api.github.com:443\n ghcr.io:443\n proxy.golang.org:443\n```\n\n## Migration Path: Audit to Block\n\n1. **Deploy audit mode** on all workflow jobs\n2. **Review StepSecurity insights** dashboard to identify all legitimate outbound domains\n3. **Build domain allowlist** per job type (see patterns below)\n4. **Switch to block mode** with the allowlist\n5. **Monitor** for false positives — add missing domains as needed\n\n## Domain Allowlist Patterns by Job Type\n\n### Go Builds\n\n```\nproxy.golang.org:443\nsum.golang.org:443\nstorage.googleapis.com:443\n```\n\n### npm Builds\n\n```\nregistry.npmjs.org:443\n```\n\n### Docker Builds\n\n```\n# Docker Hub\nregistry-1.docker.io:443\nauth.docker.io:443\nproduction.cloudflare.docker.com:443\n\n# GitHub Container Registry\nghcr.io:443\n```\n\n### Trivy Scans\n\n```\n# GitHub Container Registry (trivy-db download)\nghcr.io:443\n```\n\n### GitHub Operations\n\n```\n# GitHub platform\ngithub.com:443\napi.github.com:443\nuploads.github.com:443\n```\n\n### Composer (PHP)\n\n```\nrepo.packagist.org:443\npackagist.org:443\ngetcomposer.org:443\n```\n\n## Full Configuration Example (Block Mode)\n\n```yaml\nname: CI\non:\n push:\n branches: [main]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - name: Harden-Runner\n uses: step-security/harden-runner@\u003csha> # vX.Y.Z\n with:\n egress-policy: block\n allowed-endpoints: >\n github.com:443\n api.github.com:443\n proxy.golang.org:443\n sum.golang.org:443\n storage.googleapis.com:443\n ghcr.io:443\n\n - uses: actions/checkout@\u003csha> # vX.Y.Z\n\n - name: Build\n run: go build ./...\n\n - name: Test\n run: go test ./...\n```\n\n## Key Points\n\n- Harden-Runner must be the **first step** in every job\n- Each job needs its **own** Harden-Runner step with a job-specific allowlist\n- Use `:443` port suffix for HTTPS endpoints in the allowlist\n- StepSecurity provides a free insights dashboard showing all detected egress for public repos\n- Pin the `step-security/harden-runner` action to a commit SHA (not a tag)\n\n## Resources\n\n- [StepSecurity Harden-Runner](https://github.com/step-security/harden-runner)\n- [StepSecurity App Dashboard](https://app.stepsecurity.io/)\n- [OpenSSF Scorecard — Token Permissions](https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3680,"content_sha256":"da06d54f6e3c0633023ad1a398ae4d301ddb6ee4b862c5637e63f2b68a5abe65"},{"filename":"references/mandatory-requirements.md","content":"# Mandatory Requirements\n\nEvery project MUST have ALL of these. Do not skip any.\n\n## README Badges\n\nEvery project README.md MUST display these badges at the top:\n\n```markdown\n\u003c!-- Row 1: CI/Quality -->\n[![CI](https://github.com/ORG/REPO/actions/workflows/ci.yml/badge.svg)](https://github.com/ORG/REPO/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/ORG/REPO/graph/badge.svg)](https://codecov.io/gh/ORG/REPO)\n\n\u003c!-- Row 2: Security -->\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ORG/REPO/badge)](https://securityscorecards.dev/viewer/?uri=github.com/ORG/REPO)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/PROJECT_ID/badge)](https://www.bestpractices.dev/projects/PROJECT_ID)\n```\n\n| Badge | URL Pattern |\n|-------|-------------|\n| CI Status | `github.com/ORG/REPO/actions/workflows/ci.yml/badge.svg` |\n| Codecov | `codecov.io/gh/ORG/REPO/graph/badge.svg` |\n| OpenSSF Scorecard | `api.securityscorecards.dev/projects/github.com/ORG/REPO/badge` |\n| OpenSSF Best Practices | `www.bestpractices.dev/projects/PROJECT_ID/badge` |\n\n## CI/CD Workflows\n\nEvery GitHub project MUST cover these capabilities. Each can be supplied\nas a dedicated workflow file OR as a job in another workflow that delegates\nto the netresearch reusable workflow.\n\n| Capability | Dedicated file | Reusable-workflow alternative |\n|------------|----------------|-------------------------------|\n| CI | `ci.yml` | `uses: netresearch/typo3-ci-workflows/.github/workflows/ci.yml@…` |\n| CodeQL | `codeql.yml` | `uses: netresearch/.github/.github/workflows/codeql.yml@…` (or `github/codeql-action`) |\n| Scorecard | `scorecard.yml` | `uses: netresearch/.github/.github/workflows/scorecard.yml@…` (or `ossf/scorecard-action`) |\n| Dependency Review | `dependency-review.yml` | `uses: netresearch/.github/.github/workflows/dependency-review.yml@…` (or `actions/dependency-review-action`) |\n| Security (composer audit + SBOM) | `security.yml` | `uses: netresearch/typo3-ci-workflows/.github/workflows/security.yml@…` |\n\n## CI Must Include\n\n| Requirement | Implementation |\n|-------------|----------------|\n| Coverage upload | `codecov/codecov-action` after tests |\n| Security audit | `composer audit` / `npm audit` / `govulncheck` |\n| SHA-pinned actions | All actions use full SHA with version comment |\n\n## Supply Chain Security (Releases)\n\nSupply chain security controls MUST block releases when violated.\n\n| Control | Implementation | Blocks Release? |\n|---------|----------------|-----------------|\n| SLSA Provenance | `slsa-github-generator` workflow | **YES** |\n| Signed Tags | `git tag -s` before `gh release create` | **YES** |\n| SBOM Generation | `anchore/sbom-action` or `cyclonedx` | **YES** |\n| Dependency Audit | `composer audit` / `npm audit` fails CI | **YES** |\n\n### Release Workflow Order\n\n```\n1. CI passes (tests, lint, security audit)\n ↓\n2. Create SIGNED tag locally: git tag -s vX.Y.Z\n ↓\n3. Push signed tag: git push origin vX.Y.Z\n ↓\n4. Create release on existing tag: gh release create vX.Y.Z\n ↓\n5. SLSA provenance workflow triggers (workflow_run)\n ↓\n6. Provenance attestation uploaded to release\n ↓\n7. SBOM generated and uploaded\n```\n\n**NEVER** use `gh release create` without a pre-existing signed tag.\n\n### CI Integration for Supply Chain\n\nAdd to `.github/workflows/release.yml`:\n\n```yaml\n# Pre-release checks (block release if failed)\n- name: Security Audit\n run: |\n composer audit --format=plain || exit 1\n npm audit --audit-level=high || exit 1\n\n# SBOM generation\n- name: Generate SBOM\n uses: anchore/sbom-action@v0\n with:\n artifact-name: sbom.spdx.json\n output-file: sbom.spdx.json\n\n- name: Upload SBOM to release\n run: gh release upload ${{ github.ref_name }} sbom.spdx.json\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n```\n\n### Verification of Supply Chain Artifacts\n\nBefore announcing a release, verify:\n\n- [ ] Release tag is GPG/SSH signed: `git tag -v vX.Y.Z`\n- [ ] SLSA provenance exists: `multiple.intoto.jsonl` in release assets\n- [ ] SBOM exists: `sbom.spdx.json` or `sbom.cyclonedx.json` in release assets\n- [ ] No HIGH/CRITICAL CVEs: `composer audit` / `npm audit` clean\n\nSee `slsa-provenance.md` for detailed SLSA implementation guide.\n\n## OpenSSF Registration\n\n1. Register at https://www.bestpractices.dev/en/projects/new\n2. Note the Project ID assigned after registration\n3. Add badge to README with correct PROJECT_ID\n4. Run Scorecard workflow to generate initial score\n\n## Codecov Setup\n\n1. Enable Codecov for the repository at codecov.io\n2. Collect coverage from ALL test suites (not just unit tests):\n\n| Test Suite | Coverage Command | Output File |\n|------------|------------------|-------------|\n| Unit | `phpunit -c UnitTests.xml --coverage-clover` | `.Build/coverage/unit.xml` |\n| Integration | `phpunit -c IntegrationTests.xml --coverage-clover` | `.Build/coverage/integration.xml` |\n| E2E | `phpunit -c E2ETests.xml --coverage-clover` | `.Build/coverage/e2e.xml` |\n| Functional | `phpunit -c FunctionalTests.xml --coverage-clover` | `.Build/coverage/functional.xml` |\n| JavaScript | `npm run test:coverage` | `coverage/lcov.info` |\n\n3. Upload ALL coverage files to Codecov:\n\n```yaml\n- uses: codecov/codecov-action@SHA # vX.Y.Z\n with:\n token: ${{ secrets.CODECOV_TOKEN }} # MANDATORY\n files: .Build/coverage/unit.xml,.Build/coverage/integration.xml,.Build/coverage/e2e.xml,coverage/lcov.info\n fail_ci_if_error: false\n```\n\n### CODECOV_TOKEN\n\n**Never rely on tokenless uploads.** They fail for protected branches and are unreliable.\n\n| Requirement | Implementation | Why |\n|-------------|----------------|-----|\n| Token in secrets | Add `CODECOV_TOKEN` to repo or org secrets | Authentication |\n| Token in workflow | `token: ${{ secrets.CODECOV_TOKEN }}` | Required for protected branches |\n| Org-level secret | Preferred for consistency across repos | Single point of management |\n\nGet token from: https://app.codecov.io/gh/ORG/REPO/settings\n\n```bash\n# Organization-level (covers all repos)\ngh secret set CODECOV_TOKEN --org netresearch --visibility all\n\n# Or repository-level\ngh secret set CODECOV_TOKEN --repo OWNER/REPO\n```\n\n### JavaScript Coverage (projects with JS/TS)\n\nWhen a project contains JavaScript or TypeScript files:\n\n1. **vitest.config.js** MUST include lcov reporter:\n\n```javascript\ncoverage: {\n provider: 'v8',\n reporter: ['text', 'json', 'html', 'lcov'], // lcov REQUIRED for Codecov\n reportsDirectory: 'coverage',\n}\n```\n\n2. **CI workflow** MUST include JavaScript test job:\n\n```yaml\n- uses: actions/setup-node@SHA # vX.Y.Z\n with:\n node-version: '22'\n- run: npm install\n- run: npm run test:coverage\n```\n\n3. **Codecov upload** MUST include `coverage/lcov.info`\n\n## Verification Checklist\n\nBefore marking enterprise-readiness complete, verify ALL:\n\n- [ ] README has CI badge linking to workflow\n- [ ] README has Codecov badge (not \"unknown\")\n- [ ] README has OpenSSF Scorecard badge (correct URL with `api.securityscorecards.dev`)\n- [ ] README has OpenSSF Best Practices badge (correct PROJECT_ID, not placeholder)\n- [ ] `.github/workflows/ci.yml` exists and uploads coverage\n- [ ] `.github/workflows/codeql.yml` exists\n- [ ] `.github/workflows/scorecard.yml` exists\n- [ ] Codecov shows actual coverage percentage\n- [ ] Scorecard shows actual score\n\n**If any badge shows \"unknown\", \"invalid\", or placeholder ID -- FIX IT. Do not proceed.**\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7418,"content_sha256":"3add2004ce60ff8446a9838ebe849648efad3c6c233d4d8620b5c7a326e7113f"},{"filename":"references/npm-pnpm-supply-chain.md","content":"# npm / pnpm Supply-Chain Hardening\n\nLifecycle-script execution, version quarantine, and lockfile defenses for JavaScript dependencies. Applies to any repo using `npm`, `pnpm`, or `yarn`, with concrete pnpm 10.26+ / 11+ configuration.\n\n## Why this matters\n\nLifecycle scripts (`preinstall`, `install`, `postinstall`) are the #1 npm supply-chain attack vector. A compromised maintainer account or hijacked publish lets the attacker run arbitrary code on every developer machine and CI runner that installs the package — without any source change visible in the repo.\n\nDefenses must be layered. No single control catches every attack class:\n\n| Layer | Stops |\n|-------|-------|\n| Committed lockfile + `--frozen-lockfile` | Version substitution: a hijacked **new** version cannot be installed without a lockfile diff visible in PR review |\n| Lifecycle-script allowlist (`allowBuilds`) | Newly added transitive dep with a malicious postinstall — install fails for unlisted names |\n| Lifecycle-script **denylist** (`allowBuilds.foo: false`) | A hijacked version of a known dep that previously needed scripts but no longer does (because prebuilt platform binaries make the postinstall redundant) |\n| Release-age quarantine (`minimumReleaseAge`) | A freshly hijacked version that hasn't been flagged yet — buys time for the community to detect and unpublish |\n| Dep-PR human review | Integrity-hash changes are visible in the lockfile diff |\n| npm provenance (`--require-attestation`, sigstore) | Tampering between source and registry — but most packages still don't publish provenance, so tree-wide enforcement isn't yet practical |\n\n## pnpm 11+ canonical configuration\n\nSettings live in `pnpm-workspace.yaml` (not `.npmrc` / `.pnpmrc` — those are for client behavior).\n\n```yaml\npackages:\n - '.'\n # other workspaces\n\n# Fail install when any dependency wants to run lifecycle scripts that\n# aren't explicitly handled in allowBuilds. Default since pnpm 11.\nstrictDepBuilds: true\n\n# Per-package, per-version allow/deny map for lifecycle scripts.\n# - true: scripts may run\n# - false: scripts must NOT run (preferred for packages with prebuilt binaries)\n# Supports version ranges: \"[email protected] || 21.6.5: true\"\nallowBuilds:\n esbuild: false\n \"@parcel/watcher\": false\n # electron: true # genuinely needs its postinstall\n\n# 7-day quarantine on freshly published versions (value is in MINUTES).\n# Reduces the window where a hijacked publish can land before npm/the\n# community removes it. Use minimumReleaseAgeExclude for hotfixes.\nminimumReleaseAge: 10080\n\nminimumReleaseAgeExclude:\n - [email protected]\n```\n\n### Why deny scripts for esbuild and @parcel/watcher\n\nBoth ship per-platform prebuilt binaries via `optionalDependencies`:\n\n- `@esbuild/linux-x64`, `@esbuild/darwin-arm64`, `@esbuild/linux-x64-musl`, …\n- `@parcel/watcher-linux-x64-glibc`, `@parcel/watcher-darwin-arm64`, …\n\npnpm/npm install only the matching optional dep for the current platform. The wrapper package's `postinstall` is a fallback for environments where the optional dep didn't install — when it does install (the normal case), the postinstall is redundant.\n\nSetting `allowBuilds.esbuild: false` (the `esbuild: false` entry in the `allowBuilds` map) means **even a hijacked future release of `esbuild` cannot execute code at install time**, because pnpm refuses to run its scripts regardless of version. The prebuilt binary still works because it's resolved through a separate package whose tarball is integrity-checked by the lockfile.\n\nA name-only allowlist (the deprecated `onlyBuiltDependencies: [\"esbuild\"]`) does NOT have this property: it permits scripts for any version named `esbuild`, including a hijacked one.\n\n## Engine constraints must track feature floor\n\nIf your config uses `allowBuilds` (added in pnpm 10.26) or `minimumReleaseAge` (added in 10.16), require those versions in `package.json`:\n\n```json\n{\n \"engines\": {\n \"node\": \">= 22.0.0\",\n \"pnpm\": \">= 10.26.0\"\n }\n}\n```\n\nWithout this, an older pnpm silently ignores the new keys and the hardening becomes a no-op. Combined with `engine-strict=true` in `.npmrc` / `.pnpmrc`, pnpm will refuse to install on too-old versions instead of degrading silently.\n\n## Deprecated fields (avoid)\n\nThe following pnpm 10 settings were superseded by `allowBuilds` and **removed in pnpm 11**:\n\n| Removed (pnpm 11) | Replacement |\n|---|---|\n| `onlyBuiltDependencies: [\"esbuild\"]` | `allowBuilds: { esbuild: true }` |\n| `neverBuiltDependencies: [\"core-js\"]` | `allowBuilds: { core-js: false }` |\n| `ignoredBuiltDependencies: [\"esbuild\"]` | `allowBuilds: { esbuild: false }` |\n| `onlyBuiltDependenciesFile: \"...\"` | n/a — inline in `pnpm-workspace.yaml` |\n\n`ignoredBuilds` is **not** a valid pnpm setting (despite occasional AI reviewer suggestions). The legacy field was named `ignoredBuiltDependencies`.\n\n## Reproducibility — pin the package manager\n\n`corepack prepare pnpm@latest --activate` in Dockerfiles or CI is a CI-stability footgun **only when** you also fail to adopt the new security defaults that come with each major. The right move is both:\n\n1. **Pin the version** so upgrades are explicit PRs, not silent breakage on the next image build:\n\n ```json\n {\n \"packageManager\": \"[email protected]\"\n }\n ```\n\n Then in the Dockerfile:\n\n ```dockerfile\n RUN corepack enable && corepack prepare --activate\n ```\n\n (no version argument — corepack reads `packageManager` from `package.json`).\n\n2. **Adopt the new security defaults** as they ship. pnpm 11 flipped `strictDepBuilds` to default-true, which is the right behavior — projects that hadn't configured `allowBuilds` saw CI break immediately. That break was the system working as designed; the fix is to configure `allowBuilds`, not to revert pnpm.\n\n## Verification\n\nA working hardened config produces this on `pnpm install --frozen-lockfile`:\n\n```\nDone in 3.3s using pnpm v11.0.9\n```\n\nNo `[ERR_PNPM_IGNORED_BUILDS]` warning, no `Run \"pnpm approve-builds\"` prompt. If either appears, `allowBuilds` is missing entries or the pnpm version is too old.\n\nTo audit a repo end-to-end:\n\n```bash\n# Settings are present\nyq -e '.strictDepBuilds == true and .allowBuilds and .minimumReleaseAge' pnpm-workspace.yaml\n\n# Engine floor is high enough\njq -e '.engines.pnpm | test(\"\\\\^?(10\\\\.(2[6-9]|[3-9][0-9])|[1-9][0-9]+)\")' package.json\n\n# Lockfile is committed and frozen install works\ntest -f pnpm-lock.yaml && pnpm install --frozen-lockfile\n```\n\n## npm and yarn equivalents\n\n| Capability | pnpm 11+ | npm | yarn (Berry) |\n|---|---|---|---|\n| Block scripts globally | `allowBuilds` (deny by omission with `strictDepBuilds`) | `ignore-scripts=true` in `.npmrc` (all-or-nothing) | `enableScripts: false` in `.yarnrc.yml` |\n| Per-package allow | `allowBuilds: { pkg: true }` | n/a | `--ignore-scripts` plus selective post-install |\n| Per-version pinning | `allowBuilds: { \"[email protected]\": true }` | n/a | n/a |\n| Release-age quarantine | `minimumReleaseAge` | n/a (proposed; not landed) | n/a |\n| Provenance verification | `verify-deps-before-run` (limited) | `npm install --require-attestation` | n/a |\n\nFor npm, the practical hardening today is:\n\n```ini\n# .npmrc\nignore-scripts=true\naudit-level=high\n```\n\n…then explicitly run any required postinstalls in CI/build scripts. This trades convenience for safety; pnpm's per-package `allowBuilds` is the more ergonomic answer when you have the choice.\n\n## Related\n\n- `slsa-provenance.md` — provenance and attestations for your **own** releases (counterpart to consuming attested upstream packages)\n- `signed-releases.md` — cosign / GPG signing for release artifacts\n- `reproducible-builds.md` — locked toolchains and dependency pinning\n- `mandatory-requirements.md` — checklist incorporation\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7766,"content_sha256":"9bca5d184c37934b6b8398d4e17b2541eb2e9e8d2016cabdf846e4cbaf5578b5"},{"filename":"references/openssf-badge-baseline.md","content":"# OpenSSF OSPS Baseline Levels (1/2/3)\n\n> The OSPS Baseline is a **separate** framework from the original Best Practices Badge (Passing/Silver/Gold).\n> Both frameworks are managed on the same bestpractices.dev platform but produce **different badges**.\n\n## Overview\n\n| Framework | Levels | Badge URL Pattern | Focus |\n|-----------|--------|-------------------|-------|\n| Best Practices | Passing, Silver, Gold | `/projects/{ID}/badge` | Project maturity & practices |\n| OSPS Baseline | 1, 2, 3 | `/projects/{ID}/baseline` | Security posture (OSPS standard) |\n\n**Both badges should be displayed** in your README. A project can achieve both simultaneously.\n\n## Badge Display\n\n```markdown\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/PROJECT_ID/badge)](https://www.bestpractices.dev/projects/PROJECT_ID)\n[![OpenSSF Baseline](https://www.bestpractices.dev/projects/PROJECT_ID/baseline)](https://www.bestpractices.dev/projects/PROJECT_ID)\n```\n\n## Baseline Level 1 (Foundation)\n\nCore security practices every project should meet.\n\n### Access Control (OSPS-AC)\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-AC-01.01 | `osps_ac_01_01` | Multi-factor authentication for maintainers |\n| OSPS-AC-02.01 | `osps_ac_02_01` | Defined roles with access control |\n| OSPS-AC-03.01 | `osps_ac_03_01` | Enforce access control on repositories |\n\n### Build & Release (OSPS-BR)\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-BR-01.01 | `osps_br_01_01` | Automated build process |\n| OSPS-BR-02.01 | `osps_br_02_01` | Dependency management |\n| OSPS-BR-03.01 | `osps_br_03_01` | Integrity verification for dependencies |\n\n### Documentation (OSPS-DO)\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-DO-01.01 | `osps_do_01_01` | Security policy documented |\n| OSPS-DO-02.01 | `osps_do_02_01` | Contribution guidelines |\n\n### Governance (OSPS-GV)\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-GV-01.01 | `osps_gv_01_01` | Project governance documented |\n\n### Legal (OSPS-LE)\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-LE-01.01 | `osps_le_01_01` | License clearly identified |\n\n### Quality Assurance (OSPS-QA)\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-QA-01.01 | `osps_qa_01_01` | Automated testing exists |\n| OSPS-QA-02.01 | `osps_qa_02_01` | Tests run in CI |\n| OSPS-QA-03.01 | `osps_qa_03_01` | Static analysis in CI |\n\n### Vulnerability Management (OSPS-VM)\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-VM-01.01 | `osps_vm_01_01` | Process for reporting vulnerabilities |\n| OSPS-VM-02.01 | `osps_vm_02_01` | Dependency vulnerability monitoring |\n| OSPS-VM-03.01 | `osps_vm_03_01` | Timely vulnerability response |\n\n---\n\n## Baseline Level 2 (Intermediate)\n\nBuilds on Level 1 with stronger controls.\n\n### Additional Criteria\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-AC-01.02 | `osps_ac_01_02` | Phishing-resistant MFA (FIDO2/WebAuthn) |\n| OSPS-AC-02.02 | `osps_ac_02_02` | Least privilege access |\n| OSPS-BR-01.02 | `osps_br_01_02` | Reproducible builds or build provenance |\n| OSPS-BR-02.02 | `osps_br_02_02` | Pinned dependencies |\n| OSPS-DO-01.02 | `osps_do_01_02` | Security design documentation |\n| OSPS-GV-01.02 | `osps_gv_01_02` | Succession planning |\n| OSPS-QA-02.02 | `osps_qa_02_02` | Code coverage tracking |\n| OSPS-QA-04.01 | `osps_qa_04_01` | Dynamic analysis / fuzz testing |\n| OSPS-VM-02.02 | `osps_vm_02_02` | Automated vulnerability scanning |\n| OSPS-VM-04.01 | `osps_vm_04_01` | Security advisories published |\n\n---\n\n## Baseline Level 3 (Advanced)\n\nHighest security posture. Some criteria are challenging for solo maintainers.\n\n### Additional Criteria\n\n| Criterion | Field Name | Requirement |\n|-----------|-----------|-------------|\n| OSPS-BR-01.03 | `osps_br_01_03` | SLSA Level 3 provenance |\n| OSPS-BR-03.02 | `osps_br_03_02` | Signed releases |\n| OSPS-QA-05.01 | `osps_qa_05_01` | Architecture documentation |\n| OSPS-QA-06.01 | `osps_qa_06_01` | Threat model documented |\n| OSPS-QA-07.01 | `osps_qa_07_01` | Two-person review for changes |\n| OSPS-VM-01.02 | `osps_vm_01_02` | Private vulnerability reporting |\n\n### Solo Maintainer Blockers at Level 3\n\n| Criterion | Issue | Mitigation |\n|-----------|-------|------------|\n| `OSPS-QA-07.01` | Requires two-person review | Same challenge as Gold `two_person_review`. Use automated review tools (Copilot, CodeQL) as compensating controls. Mark Unmet with justification. |\n\n---\n\n## Field Naming Convention\n\n**CRITICAL**: The bestpractices.dev HTML forms use a different naming convention than the display names:\n\n| Display Name | Form Field Name |\n|-------------|----------------|\n| `OSPS-AC-01.01` | `osps_ac_01_01_status` / `osps_ac_01_01_justification` |\n| `OSPS-BR-03.02` | `osps_br_03_02_status` / `osps_br_03_02_justification` |\n\n**Pattern**: Replace hyphens with underscores, dots with underscores, all lowercase.\n\nWhen submitting programmatically, use the form field name format (e.g., `project[osps_ac_01_01_status]`).\n\n---\n\n## Relationship to Best Practices Badge\n\nThe two frameworks overlap significantly:\n\n| OSPS Baseline | Overlaps With Best Practices |\n|---------------|------------------------------|\n| OSPS-AC-01.01 (MFA) | Gold: `require_2FA` |\n| OSPS-QA-07.01 (Two-person review) | Gold: `two_person_review` |\n| OSPS-VM-01.01 (Vuln reporting) | Passing: `vulnerability_report_process` |\n| OSPS-BR-03.02 (Signed releases) | Silver: `signed_releases` |\n| OSPS-QA-01.01 (Automated tests) | Passing: `test` |\n\n**Tip**: If you've achieved Silver or Gold, most Baseline criteria are already Met. Focus on the non-overlapping OSPS-specific criteria.\n\n---\n\n## Checking Baseline Status\n\n```bash\n# Check current baseline level via API\ncurl -s \"https://www.bestpractices.dev/projects/PROJECT_ID.json\" | \\\n jq '{badge_level, osps_baseline_level: .tiered_percentage}'\n\n# Or with cache-busting\ncurl -s \"https://www.bestpractices.dev/projects/PROJECT_ID.json?_=$(date +%s)\" | \\\n jq '{badge_level, osps_baseline_level: .tiered_percentage}'\n```\n\n---\n\n## Resources\n\n- [OSPS Baseline Standard](https://baseline.openssf.org/)\n- [bestpractices.dev Baseline Criteria](https://www.bestpractices.dev/en/criteria)\n- [OpenSSF Security Baseline](https://openssf.org/projects/security-baseline/)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6505,"content_sha256":"d93ee0968b2c0097a687d487b39a953ce578426df447b089e36d667a28a42fc9"},{"filename":"references/openssf-badge-gold.md","content":"# OpenSSF Best Practices Badge - Gold Level\n\nComplete checklist for Gold level certification aligned with\n[OpenSSF Best Practices Badge](https://www.bestpractices.dev/) criteria.\n\n## Prerequisites\n\n| Criterion | Requirement | Status |\n|-----------|-------------|--------|\n| achieve_passing | Passing level (100%) achieved | Required |\n| achieve_silver | Silver level (100%) achieved | Required |\n\n**Note:** Gold level represents the highest tier of project maturity and security.\nMost criteria require organizational commitment beyond individual maintainer capabilities.\n\n---\n\n## Basics (6 criteria)\n\n### Project Identity (2 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| identification | \"Is the project uniquely identifiable?\" | Check for unique project identity | Ensure distinct name, namespace |\n| contributors_unassociated | \"Are there unassociated contributors?\" | Check contributor affiliations | Have contributors from 2+ organizations |\n\n**Why Unassociated Contributors Matter:**\n- Reduces organizational bias in decision-making\n- Demonstrates project is not controlled by single entity\n- Required for Gold: 2+ significant contributors from different organizations\n\n### Licensing (3 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| copyright_per_file | \"Does each file have copyright notice?\" | Grep for copyright headers | Add headers to all source files |\n| license_per_file | \"Does each file have license notice?\" | Grep for license headers | Add SPDX identifiers to all files |\n| bus_factor | \"Is bus factor >= 2?\" | Analyze commit distribution | Ensure 2+ people can maintain project |\n\n**SPDX License Header:**\n```go\n// SPDX-License-Identifier: MIT\n// Copyright (c) 2024 Project Authors\n\npackage main\n```\n\n**Adding Headers (Go):**\n```bash\n# Use addlicense tool\ngo install github.com/google/addlicense@latest\naddlicense -c \"Project Authors\" -l mit .\n```\n\n---\n\n## Change Control (4 criteria)\n\n### Version Control (4 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| repo_distributed | \"Is the repository distributed?\" | Check VCS type | Use Git (distributed by design) |\n| require_2FA | \"Is 2FA required for collaborators?\" | Check GitHub org settings | Enable 2FA requirement for org |\n| secure_2FA | \"Is 2FA using secure methods?\" | Check for hardware keys | Prefer FIDO2/WebAuthn over TOTP |\n\n**GitHub 2FA Enforcement:**\n```\nSettings → Organization → Security → Require 2FA for everyone\n```\n\n---\n\n## Quality (7 criteria)\n\n### Code Review (2 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| code_review_standards | \"Are there code review standards?\" | Check CONTRIBUTING.md | Document review expectations |\n| two_person_review | \"Do changes require 2-person review?\" | Check branch protection | Require 2 reviewers for main branch |\n\n**Two-Person Review:**\n```bash\n# Set via GitHub API\ngh api repos/{owner}/{repo}/branches/main/protection \\\n -X PUT \\\n -f required_approving_review_count=2\n```\n\n**Note:** This is challenging for solo maintainers. Options:\n- Partner with another project for cross-review\n- Use bot reviewers (limited effectiveness)\n- Mark as N/A with justification\n\n### Build (1 criterion)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| build_reproducible | \"Is the build reproducible?\" | Build twice, compare checksums | Achieve bit-for-bit reproducible builds |\n\n**N/A for Source-Distributed Libraries:**\n\nProjects distributed as source code (not compiled binaries) may mark this criterion as **N/A**:\n\n| Project Type | Status | Justification |\n|-------------|--------|---------------|\n| PHP Composer packages | N/A | Source code distributed via Composer. No compiled artifact. Reproducibility is the consuming application's responsibility via `composer.lock`. |\n| Python PyPI packages | N/A | Source distribution or wheels built from source. Reproducibility via `requirements.txt` / `pip freeze`. |\n| npm packages | N/A | Source distributed via npm registry. Reproducibility via `package-lock.json`. |\n| Go binaries | Must Meet | Compiled binaries require reproducible builds. |\n| Rust binaries | Must Meet | Compiled binaries require reproducible builds. |\n| Docker images | Must Meet | Container images require reproducible builds. |\n\n**Reproducible Builds (Go):**\n```bash\n# Build with all reproducibility flags\nCGO_ENABLED=0 go build \\\n -trimpath \\\n -ldflags '-s -w -buildid= -extldflags=-static' \\\n -o binary .\n\n# Verify\nsha256sum binary # Should be identical across builds\n```\n\n### Testing (4 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| test_invocation | \"Can tests be invoked with standard command?\" | Check test documentation | Use `go test`, `npm test`, etc. |\n| test_continuous_integration | \"Are tests run in CI?\" | Check CI configuration | All PRs must pass tests |\n| test_statement_coverage90 | \"Is statement coverage >= 90%?\" | Check coverage reports | Enforce 90% coverage threshold |\n| test_branch_coverage80 | \"Is branch coverage >= 80%?\" | Check coverage reports | Measure and enforce branch coverage |\n\n**90% Statement + 80% Branch Coverage (Go):**\n```yaml\n- name: Run tests with coverage\n run: |\n go test -coverprofile=coverage.out -covermode=atomic ./...\n\n # Statement coverage\n STMT=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%')\n if (( $(echo \"$STMT \u003c 90\" | bc -l) )); then\n echo \"Statement coverage $STMT% below 90%\"\n exit 1\n fi\n\n # For branch coverage, use more sophisticated tools\n # gocov, gocover-cobertura + branch analysis\n```\n\n---\n\n## Security (5 criteria)\n\n### Cryptography (2 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| crypto_used_network | \"Is strong crypto used for network?\" | Check TLS implementation | Use TLS 1.2+ with strong ciphers |\n| crypto_tls12 | \"Is TLS 1.2 or higher enforced?\" | Check minimum TLS version | Set MinVersion to TLS 1.2 |\n\n### Hardening (3 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| hardened_site | \"Is the project website hardened?\" | Check security headers | Add CSP, HSTS, X-Frame-Options |\n| security_review | \"Has there been a security review?\" | Check for audit reports | Conduct or commission security audit |\n| hardening | \"Are hardening techniques documented?\" | Check security documentation | Document all hardening measures |\n\n**Security Review Options:**\n1. **Internal review** - Documented security code review\n2. **External audit** - Third-party security assessment\n3. **Bug bounty** - Public security testing program\n4. **OSS security programs** - OSTIF, Trail of Bits grants\n\n---\n\n## Analysis (2 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| dynamic_analysis | \"Is dynamic analysis performed?\" | Check for runtime testing | Fuzz testing, DAST, sanitizers |\n| dynamic_analysis_enable_assertions | \"Are assertions enabled in testing?\" | Check test configuration | Run tests with assertions enabled |\n\n**Dynamic Analysis (Go):**\n```yaml\n# Fuzz testing\n- name: Fuzz tests\n run: go test -fuzz=. -fuzztime=60s ./...\n\n# Race detector\n- name: Race detection\n run: go test -race ./...\n\n# Memory sanitizer (if applicable)\n- name: Address sanitizer\n run: |\n CC=clang go test -msan ./...\n```\n\n---\n\n## Total: 24 criteria (plus prerequisites)\n\n**Gold Level Requirements Summary:**\n- Passing level: 100%\n- Silver level: 100%\n- Gold criteria: 24 additional\n\n---\n\n## Critical Gold Requirements\n\n### Organizational (Hard for Solo Maintainers)\n\n| Requirement | Challenge | Mitigation |\n|-------------|-----------|------------|\n| 2-person review | Can't self-approve | Partner projects, bot reviewers |\n| Bus factor >= 2 | Single maintainer | Recruit co-maintainers |\n| Unassociated contributors | Small team | Encourage external contributions |\n| 2FA enforcement | Org setting | Document 2FA usage |\n\n### Technical (Achievable)\n\n| Requirement | Effort | Tools |\n|-------------|--------|-------|\n| 90% coverage | High | gocov, Codecov |\n| 80% branch coverage | High | Advanced coverage tools |\n| Reproducible builds | Medium | -trimpath, -buildid= |\n| Security review | High | OSTIF, self-audit |\n| Copyright headers | Low | addlicense |\n\n---\n\n## Gold Level Roadmap\n\n### Phase 1: Technical Foundation\n1. [ ] Achieve 90% statement coverage\n2. [ ] Achieve 80% branch coverage\n3. [ ] Implement reproducible builds\n4. [ ] Add copyright/license headers to all files\n\n### Phase 2: Process Requirements\n1. [ ] Document code review standards\n2. [ ] Enable 2FA for all contributors\n3. [ ] Conduct security review (or self-audit)\n4. [ ] Create \"good first issue\" tasks\n\n### Phase 3: Organizational Growth\n1. [ ] Recruit second maintainer\n2. [ ] Attract external contributors\n3. [ ] Establish two-person review (or document exception)\n4. [ ] Complete Gold certification\n\n---\n\n## Files Needed for Gold\n\n| File | Purpose | Gold Criteria |\n|------|---------|---------------|\n| All files | Copyright headers | copyright_per_file |\n| All files | SPDX identifiers | license_per_file |\n| CONTRIBUTING.md | Review standards | code_review_standards |\n| SECURITY.md | Audit documentation | security_review |\n| README.md | Standard test commands | test_invocation |\n\n---\n\n## Exceptions and Justifications\n\nSome Gold criteria may not apply to all projects. Document exceptions clearly:\n\n```markdown\n## OpenSSF Best Practices Badge - Gold Exceptions\n\n### two_person_review (N/A)\nJustification: Solo maintainer project. Compensating controls:\n- All changes go through CI with comprehensive testing\n- CodeQL and security scanning on all PRs\n- Regular self-review of security-critical changes\n\n### contributors_unassociated (N/A)\nJustification: Specialized project with limited contributor pool.\nCompensating controls:\n- Open to contributions from any organization\n- No organizational restrictions on contribution\n```\n\n---\n\n## URL-Required Justifications\n\n**CRITICAL**: Many Gold criteria require `https://` URLs in the justification text.\nWithout URLs, criteria show \"Warning: URL required, but no URL found\" even when status is \"Met\".\n\nGold criteria that commonly need URLs:\n- `bus_factor` — link to contributors graph\n- `contributors_unassociated` — link to Contributors.md or contributor profiles\n- `hardened_site` — link to project repository\n- `hardening` — link to CI workflow demonstrating hardening measures\n\n**Rule**: Always include at least one `https://` URL in every justification.\n\n---\n\n## Verification Commands\n\n```bash\n# Check copyright headers\ngrep -rL \"Copyright\" --include=\"*.go\" .\n\n# Check SPDX identifiers\ngrep -rL \"SPDX-License-Identifier\" --include=\"*.go\" .\n\n# Check coverage\ngo test -coverprofile=c.out ./... && go tool cover -func=c.out | grep total\n\n# Verify reproducible build\nsha256sum binary1 binary2 # Should match\n\n# List good first issues\ngh issue list --label \"good first issue\"\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11494,"content_sha256":"247b70eb32e19b763c9f303d6a15891147e84505e330f1c826b39ab36135f01f"},{"filename":"references/openssf-badge-silver.md","content":"# OpenSSF Best Practices Badge - Silver Level\n\nComplete checklist for Silver level certification aligned with\n[OpenSSF Best Practices Badge](https://www.bestpractices.dev/) criteria.\n\n## Prerequisites\n\n| Criterion | Requirement | Status |\n|-----------|-------------|--------|\n| achieve_passing | Passing level (100%) achieved | Required |\n\n---\n\n## Basics (17 criteria)\n\n### Governance & Community (6 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| dco | \"Does the project require a Developer Certificate of Origin (DCO)?\" | Check CONTRIBUTING.md for DCO requirement | Add DCO sign-off requirement to CONTRIBUTING.md |\n| governance | \"Is there a documented governance model?\" | Check GOVERNANCE.md exists | Create GOVERNANCE.md with decision-making process |\n| code_of_conduct | \"Does the project have a code of conduct?\" | Check CODE_OF_CONDUCT.md exists | Adopt Contributor Covenant v3.0 |\n| roles_responsibilities | \"Are roles and responsibilities documented?\" | Check GOVERNANCE.md for role definitions | Document maintainer, contributor, security team roles |\n| access_continuity | \"Is there access continuity (bus factor > 1)?\" | Check for multiple maintainers with write access | Add backup maintainers with repository access |\n| bus_factor | \"Is the bus factor >= 2?\" | Check contributor statistics | Ensure 2+ people understand critical code |\n\n**DCO Implementation:**\n```markdown\n\u003c!-- In CONTRIBUTING.md -->\n## Developer Certificate of Origin\n\nAll contributions must be signed off using the DCO:\n\ngit commit -s -m \"Your commit message\"\n\nThis certifies you wrote the code or have the right to submit it under the project license.\n```\n\n**Enforcement (GitHub Action):**\n```yaml\n# .github/workflows/dco.yml\nname: DCO Check\non: [pull_request]\njobs:\n dco:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n - uses: dcoapp/app@v1\n```\n\n### Documentation (8 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| documentation_roadmap | \"Is there a project roadmap?\" | Check ROADMAP.md or GitHub Projects | Create ROADMAP.md or use GitHub milestones |\n| documentation_architecture | \"Is the software architecture documented?\" | Check ARCHITECTURE.md | Create system architecture documentation |\n| documentation_security | \"Are security considerations documented?\" | Check SECURITY.md for design principles | Add security design section to SECURITY.md |\n| documentation_quick_start | \"Is there a quick start guide?\" | Check README.md for getting started | Add quick start section to README |\n| documentation_current | \"Is documentation reasonably current?\" | Compare docs date vs code changes | Update docs with each release |\n| documentation_achievements | \"Are achievements/capabilities documented?\" | Check feature documentation | Document what the software can do |\n| accessibility_best_practices | \"Does the project follow accessibility best practices?\" | N/A for non-UI libraries | Document accessibility approach for UI projects |\n| internationalization | \"Does the project support internationalization?\" | N/A for non-localized tools | Implement i18n if user-facing |\n\n**Architecture Documentation Template:**\n```markdown\n# Architecture\n\n## Overview\n[High-level system description]\n\n## Components\n- Component A: [Purpose]\n- Component B: [Purpose]\n\n## Data Flow\n[Diagram or description]\n\n## Security Considerations\n[Key security design decisions]\n```\n\n### Sites & Security (3 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| sites_https | \"Does the project website use HTTPS?\" | Check project URLs | Ensure all links use HTTPS |\n| sites_password_security | \"Are passwords stored securely if applicable?\" | N/A if no password storage | Use bcrypt/argon2 with proper parameters |\n| contribution_requirements | \"Are contribution requirements clear?\" | Check CONTRIBUTING.md | Document PR process, coding standards |\n\n---\n\n## Change Control (1 criterion)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| maintenance_or_update | \"Is the project actively maintained?\" | Check commit frequency, issue responses | Respond to issues within 14 days |\n\n---\n\n## Reporting (3 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| report_tracker | \"Is there a public issue tracker?\" | Check GitHub Issues enabled | Enable and monitor issue tracker |\n| vulnerability_report_credit | \"Are vulnerability reporters credited?\" | Check SECURITY.md or release notes | Add credits section to security advisories |\n| vulnerability_response_process | \"Is there a documented response process?\" | Check SECURITY.md for response timeline | Document triage, fix, disclosure process |\n\n---\n\n## Quality (19 criteria)\n\n### Coding Standards (4 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| coding_standards | \"Does the project have coding standards?\" | Check for linter configs | Document in CONTRIBUTING.md |\n| coding_standards_enforced | \"Are coding standards automatically enforced?\" | Check CI for linting steps | Add linting to CI (fail on violations) |\n| warnings | \"Are compiler warnings addressed?\" | Check build logs, linter output | Fix all warnings, use strict mode |\n| warnings_strict | \"Does the project use strict warning settings?\" | Check compiler/linter config | Enable all recommended warnings |\n\n**Enforcement Example (Go):**\n```yaml\n- name: Lint\n run: golangci-lint run --timeout 5m\n # Fails on any warning\n```\n\n### Build (5 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| build_standard_variables | \"Does the build use standard variables?\" | Check Makefile/build scripts | Use standard env vars (CC, CFLAGS, etc.) |\n| build_preserve_debug | \"Can debug info be preserved?\" | Check build flags | Support debug builds |\n| build_non_recursive | \"Does the build avoid recursive make?\" | Check Makefile structure | Use non-recursive make patterns |\n| build_repeatable | \"Is the build repeatable/deterministic?\" | Build twice, compare hashes | Use -trimpath, pin dependencies |\n| installation_common | \"Does installation follow common conventions?\" | Check install process | Support standard install locations |\n\n**Repeatable Build (Go):**\n```bash\nCGO_ENABLED=0 go build \\\n -trimpath \\\n -ldflags '-s -w -buildid=' \\\n -o binary .\n```\n\n### Installation (3 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| installation_standard_variables | \"Does installation use standard variables?\" | Check install scripts | Use PREFIX, DESTDIR conventions |\n| installation_development_quick | \"Can developers quickly install?\" | Check dev setup time | Document quick dev setup (\u003c30 min) |\n| external_dependencies | \"Are external dependencies documented?\" | Check README prerequisites | List all runtime dependencies |\n\n### Dependencies (3 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| dependency_monitoring | \"Are dependencies monitored for vulnerabilities?\" | Check Dependabot/Renovate | Enable automated dependency scanning |\n| updateable_reused_components | \"Can dependencies be updated?\" | Check lockfile management | Support dependency updates |\n| interfaces_current | \"Are external interfaces current?\" | Check API documentation | Document all public APIs |\n\n### Testing (4 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| automated_integration_testing | \"Is there automated integration testing?\" | Check CI for integration tests | Add integration test suite |\n| regression_tests_added50 | \"Are regression tests added for 50%+ bugs?\" | Check test additions in bug fix PRs | Require tests for bug fixes |\n| test_statement_coverage80 | \"Is statement coverage >= 80%?\" | Check coverage reports | Enforce 80% coverage threshold |\n| test_policy_mandated | \"Are tests required for new functionality?\" | Check PR requirements | Document test policy in CONTRIBUTING.md |\n\n**Coverage Threshold (Go):**\n```yaml\n- name: Check coverage\n run: |\n go test -coverprofile=coverage.out ./...\n COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%')\n if (( $(echo \"$COVERAGE \u003c 80\" | bc -l) )); then\n echo \"Coverage $COVERAGE% is below 80%\"\n exit 1\n fi\n```\n\n---\n\n## Security (13 criteria)\n\n### Secure Design (3 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| implement_secure_design | \"Does implementation follow secure design?\" | Code review for security patterns | Follow OWASP guidelines |\n| input_validation | \"Is input validated before processing?\" | Check input handling code | Validate all external input |\n| hardening | \"Are hardening techniques applied?\" | Check for security headers, minimal privileges | Document hardening measures |\n\n### Cryptography (6 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| crypto_algorithm_agility | \"Can crypto algorithms be replaced?\" | Check crypto abstraction | Use algorithm-agnostic interfaces |\n| crypto_credential_agility | \"Can credentials be replaced?\" | Check credential management | Support credential rotation |\n| crypto_used_network | \"Is crypto used for network comms?\" | Check network code | Use TLS for all network connections |\n| crypto_tls12 | \"Is TLS 1.2+ required?\" | Check TLS configuration | Set minimum TLS version to 1.2 |\n| crypto_certificate_verification | \"Are certificates verified?\" | Check TLS client config | Enable certificate verification |\n| crypto_verification_private | \"Are private comms verified?\" | Check for MITM protection | Use authenticated encryption |\n\n**TLS 1.2+ Enforcement (Go):**\n```go\ntlsConfig := &tls.Config{\n MinVersion: tls.VersionTLS12,\n // Go 1.14+ automatically selects optimal cipher suites\n // No need for PreferServerCipherSuites (deprecated/ignored)\n}\n```\n\n### Release Security (2 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| signed_releases | \"Are releases cryptographically signed?\" | Check for .sig files in releases | Sign with Cosign/GPG |\n| version_tags_signed | \"Are version tags signed?\" | Check `git tag -v` output | Use `git tag -s` for releases |\n\n**Signed Tags:**\n```bash\n# Create signed tag\ngit tag -s v1.0.0 -m \"Release v1.0.0\"\n\n# Verify signed tag\ngit tag -v v1.0.0\n```\n\n### Analysis (2 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| assurance_case | \"Is there a security assurance case?\" | Check security documentation | Document security claims and evidence |\n| static_analysis_common_vulnerabilities | \"Does static analysis check for common vulns?\" | Check SAST tools in CI | Use CodeQL, gosec, Semgrep |\n\n---\n\n## Analysis (2 criteria)\n\n| Criterion | Form Question | How to Verify | Implementation |\n|-----------|---------------|---------------|----------------|\n| dynamic_analysis | \"Is dynamic analysis performed?\" | Check for fuzzing, DAST | Run fuzz tests, integration security tests |\n| dynamic_analysis_unsafe | \"Does dynamic analysis check unsafe behavior?\" | Check for memory safety tests | Use race detector, sanitizers |\n\n---\n\n## Total: 55 criteria\n\n**Progress Tracking:**\n- [ ] 0-20% - Just starting\n- [ ] 21-50% - Making progress\n- [ ] 51-80% - Near completion\n- [ ] 81-99% - Final push\n- [ ] 100% - Silver achieved!\n\n---\n\n## Quick Reference: Files Needed\n\n| File | Purpose | Silver Criteria |\n|------|---------|-----------------|\n| GOVERNANCE.md | Decision-making, roles | governance, roles_responsibilities |\n| CODE_OF_CONDUCT.md | Community standards | code_of_conduct |\n| ARCHITECTURE.md | System design | documentation_architecture |\n| SECURITY.md | Security policy, design | documentation_security, assurance_case |\n| CONTRIBUTING.md | Contribution process | dco, coding_standards, test_policy |\n| ROADMAP.md | Future plans | documentation_roadmap |\n\n---\n\n## URL-Required Justifications\n\n**CRITICAL**: Many Silver criteria require `https://` URLs in the justification text.\nWithout URLs, criteria show \"Warning: URL required, but no URL found\" even when status is \"Met\".\n\nSilver criteria that commonly need URLs:\n- `dco` — link to DCO check workflow (`.github/workflows/dco.yml`)\n- `governance` — link to CONTRIBUTING.md or GOVERNANCE.md\n- `roles_responsibilities` — link to CODEOWNERS file\n- `access_continuity` — link to GitHub organization members page\n- `bus_factor` — link to contributors graph\n- `documentation_architecture` — link to architecture docs directory\n- `documentation_achievements` — link to README.md showing badges\n- `vulnerability_report_credit` — link to SECURITY.md credit section\n- `coding_standards` — link to linter config or composer.json\n- `external_dependencies` — link to dependency manifest (composer.json, go.mod, package.json)\n\n**Rule**: Always include at least one `https://` URL in every justification.\n\n---\n\n## Implementation Priority\n\n### High Priority (Blocking)\n1. DCO enforcement\n2. 80% test coverage\n3. Signed releases AND tags\n4. Integration testing\n\n### Medium Priority\n1. Architecture documentation\n2. Build reproducibility\n3. TLS 1.2+ enforcement\n4. Input validation documentation\n\n### Lower Priority (N/A for many projects)\n1. Accessibility (non-UI)\n2. Internationalization\n3. Password storage (if not applicable)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14168,"content_sha256":"f6841c95b34ae430e5d30a47f38843b8ba146b8b641f6e45b12cb360baf86029"},{"filename":"references/quick-start-guide.md","content":"# Quick Start Guide Implementation\n\n> OpenSSF Silver Badge requirement: `documentation_quick_start`\n>\n> Projects must provide a quick start guide that enables new users to get started within minutes.\n\n## Quick Start Template Structure\n\n### Essential Sections\n\n```markdown\n# Quick Start\n\n## Prerequisites\n\n- Tool 1 (version X.Y+)\n- Tool 2 (version X.Y+)\n\n## Installation\n\n### Option 1: Package Manager\n\\`\\`\\`bash\n# npm\nnpm install your-package\n\n# pip\npip install your-package\n\n# go\ngo install github.com/org/project@latest\n\\`\\`\\`\n\n### Option 2: Binary Download\n\\`\\`\\`bash\n# Download installer, inspect it, then execute (never pipe directly to sh)\ncurl -sSL https://get.example.com -o install.sh\nless install.sh # review before running\nbash install.sh\n\\`\\`\\`\n\n### Option 3: From Source\n\\`\\`\\`bash\ngit clone https://github.com/org/project\ncd project\nmake install\n\\`\\`\\`\n\n## Verify Installation\n\n\\`\\`\\`bash\nyour-tool --version\n# Expected: your-tool version X.Y.Z\n\\`\\`\\`\n\n## Your First [Action]\n\n\\`\\`\\`bash\n# Initialize\nyour-tool init\n\n# Run basic command\nyour-tool run --example\n\n# Check result\nyour-tool status\n\\`\\`\\`\n\n## Common Use Cases\n\n### Use Case 1: [Description]\n\\`\\`\\`bash\nyour-tool command1 --flag value\n\\`\\`\\`\n\n### Use Case 2: [Description]\n\\`\\`\\`bash\nyour-tool command2 --flag value\n\\`\\`\\`\n\n## Configuration\n\nCreate `config.yaml`:\n\\`\\`\\`yaml\nsetting1: value\nsetting2: value\n\\`\\`\\`\n\n## Next Steps\n\n- [Full Documentation](docs/)\n- [API Reference](docs/api.md)\n- [Examples](examples/)\n- [Troubleshooting](docs/troubleshooting.md)\n\n## Getting Help\n\n- [GitHub Issues](https://github.com/org/project/issues)\n- [Discussions](https://github.com/org/project/discussions)\n- [Discord/Slack](#invite-link)\n```\n\n## Quick Start Checklist\n\n### Required Elements (Silver Badge)\n\n- [ ] **Prerequisites listed** - Clear list of required tools/versions\n- [ ] **Installation options** - Multiple installation methods\n- [ ] **Verification step** - How to confirm successful installation\n- [ ] **First action** - Immediate hands-on example\n- [ ] **Expected output** - What users should see\n- [ ] **Next steps** - Links to deeper documentation\n\n### Quality Enhancements\n\n- [ ] **Copy-paste commands** - All commands ready to run\n- [ ] **Platform-specific** - Instructions for Linux/macOS/Windows\n- [ ] **Time estimate** - \"Get started in 5 minutes\"\n- [ ] **Video walkthrough** - Optional visual guide\n- [ ] **Troubleshooting** - Common issues and solutions\n\n## Platform-Specific Examples\n\n### Go Project Quick Start\n\n```markdown\n## Quick Start\n\n### Prerequisites\n- Go 1.21 or later\n\n### Install\n\\`\\`\\`bash\ngo install github.com/org/project@latest\n\\`\\`\\`\n\n### Verify\n\\`\\`\\`bash\nproject --version\n\\`\\`\\`\n\n### First Run\n\\`\\`\\`bash\n# Initialize a new project\nproject init my-app\n\n# Start the application\ncd my-app && project run\n\\`\\`\\`\n```\n\n### Python Project Quick Start\n\n```markdown\n## Quick Start\n\n### Prerequisites\n- Python 3.9 or later\n- pip or pipx\n\n### Install\n\\`\\`\\`bash\npip install project-name\n# or\npipx install project-name\n\\`\\`\\`\n\n### Verify\n\\`\\`\\`bash\nproject --version\n\\`\\`\\`\n\n### First Run\n\\`\\`\\`python\nfrom project import Client\n\nclient = Client()\nresult = client.do_something()\nprint(result)\n\\`\\`\\`\n```\n\n### Container Project Quick Start\n\n```markdown\n## Quick Start\n\n### Prerequisites\n- Docker 20.10 or later\n\n### Run\n\\`\\`\\`bash\ndocker run -d --name project \\\n -p 8080:8080 \\\n -v ./config:/config \\\n ghcr.io/org/project:latest\n\\`\\`\\`\n\n### Verify\n\\`\\`\\`bash\ncurl http://localhost:8080/health\n# Expected: {\"status\": \"healthy\"}\n\\`\\`\\`\n```\n\n## Verification Commands\n\nAdd these to CI to ensure quick start stays accurate:\n\n```yaml\n# .github/workflows/verify-quickstart.yml\nname: Verify Quick Start\non:\n push:\n paths:\n - 'docs/quick-start.md'\n - 'README.md'\n schedule:\n - cron: '0 0 * * 0' # Weekly\n\njobs:\n verify:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Extract and run commands\n run: |\n # Extract code blocks to a file, review, then execute\n grep -A 10 '```bash' README.md | grep -v '```' > /tmp/extracted-commands.sh\n cat /tmp/extracted-commands.sh # log commands for audit\n bash /tmp/extracted-commands.sh\n```\n\n## Badge Criteria Alignment\n\n| Criterion | Requirement | Implementation |\n|-----------|-------------|----------------|\n| documentation_quick_start | Quick start guide exists | Create QUICKSTART.md or README section |\n| documentation_basics | Basic info for potential users | Combine with quick start |\n| documentation_interface | External interface docs | Link from quick start to API docs |\n\n## Common Mistakes to Avoid\n\n1. **Assuming knowledge** - Don't skip prerequisite steps\n2. **Outdated commands** - Test quick start in CI regularly\n3. **Missing verification** - Always show expected output\n4. **No error handling** - Include troubleshooting for common issues\n5. **Platform assumptions** - Test on multiple OS\n\n## Resources\n\n- [OpenSSF Badge Quick Start Criterion](https://www.bestpractices.dev/en/criteria#1.documentation_quick_start)\n- [Write the Docs - Quick Start Guide](https://www.writethedocs.org/guide/writing/beginners-guide-to-docs/)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5173,"content_sha256":"7d0b3631c8d634af26ef6ae918f6c7a1e5a6c44b0afdc088fad6623f6986869d"},{"filename":"references/reproducible-builds.md","content":"# Reproducible Builds Implementation Guide\n\nReproducible builds ensure that anyone can verify that binaries were built from the claimed source code.\n\n## What is a Reproducible Build?\n\nA build is reproducible when:\n- Building the same source code produces bit-for-bit identical outputs\n- Any third party can verify builds match\n- Build environment is fully documented\n\n## OpenSSF Badge Requirements\n\n| Level | Criterion | Requirement |\n|-------|-----------|-------------|\n| Silver | `build_repeatable` | Build is repeatable/deterministic |\n| Gold | `build_reproducible` | Build is fully reproducible |\n\n### N/A for Source-Distributed Projects\n\nProjects that distribute **source code only** (no compiled binary artifacts) may mark\n`build_reproducible` as **N/A** with proper justification:\n\n| Language/Ecosystem | Distributed As | Status |\n|-------------------|---------------|--------|\n| PHP (Composer) | Source code | N/A — reproducibility via `composer.lock` is the application's responsibility |\n| Python (PyPI sdist) | Source tarball | N/A — reproducibility via `requirements.txt` / lockfile |\n| npm (source packages) | Source code | N/A — reproducibility via `package-lock.json` |\n| Go (binaries) | Compiled binary | **Must Meet** |\n| Rust (binaries) | Compiled binary | **Must Meet** |\n| Docker (images) | Container image | **Must Meet** |\n\n**Example N/A justification:**\n> N/A: This is a PHP library/extension distributed as source code via Composer.\n> There is no compiled build artifact. The source is reproducible by its git commit SHA.\n> Build reproducibility is the responsibility of the consuming application via composer.lock.\n\n## Implementation by Language\n\n### Go\n\nGo is particularly well-suited for reproducible builds:\n\n```bash\n# Reproducible Go build\nCGO_ENABLED=0 go build \\\n -trimpath \\\n -ldflags='-s -w -buildid=' \\\n -o binary .\n```\n\n**Flags explained:**\n- `CGO_ENABLED=0` - No C dependencies (pure Go)\n- `-trimpath` - Remove file paths from binary\n- `-ldflags='-s -w -buildid='` - Strip debug info and build ID\n\n**goreleaser config for reproducibility:**\n\n```yaml\n# .goreleaser.yml\nbuilds:\n - env:\n - CGO_ENABLED=0\n flags:\n - -trimpath\n ldflags:\n - -s -w -buildid=\n mod_timestamp: '{{ .CommitTimestamp }}'\n```\n\n### Rust\n\n```bash\n# Reproducible Rust build\nRUSTFLAGS=\"-C debuginfo=0 --remap-path-prefix=$(pwd)=.\" \\\ncargo build --release\n```\n\n### Python\n\nUse `pip-tools` for reproducible dependencies:\n\n```bash\npip-compile requirements.in --generate-hashes\npip install -r requirements.txt\n```\n\n### Node.js\n\n```bash\n# Use exact versions and lock file\nnpm ci # Not npm install\n```\n\n### Docker\n\n```dockerfile\n# Reproducible Dockerfile\nFROM golang:1.23.0-alpine@sha256:abc123...\n\n# Pin all packages\nRUN apk add --no-cache \\\n git=2.45.0-r0 \\\n make=4.4.1-r0\n```\n\n## Verification Process\n\n### 1. Document Build Environment\n\nCreate `BUILD.md`:\n\n```markdown\n# Build Environment\n\n## Requirements\n- Go 1.23.0+\n- Linux x86_64 or macOS arm64\n- Make 4.4+\n\n## Build Commands\n\\`\\`\\`bash\nmake build\n\\`\\`\\`\n\n## Expected Checksums\nSee `checksums.txt` in releases.\n```\n\n### 2. Verify Reproducibility\n\n```bash\n#!/bin/bash\n# verify-reproducible.sh\n\n# Build twice\nmake clean && make build\nmv binary binary1\nmake clean && make build\nmv binary binary2\n\n# Compare checksums\nsha256sum binary1 binary2\n\nif diff \u003c(sha256sum binary1) \u003c(sha256sum binary2 | sed 's/binary2/binary1/'); then\n echo \"✓ Build is reproducible\"\nelse\n echo \"✗ Build is NOT reproducible\"\n exit 1\nfi\n```\n\n### 3. Cross-Machine Verification\n\n```bash\n# Build in Docker for consistent environment\ndocker run --rm -v $(pwd):/src -w /src golang:1.23.0-alpine \\\n go build -trimpath -ldflags='-s -w -buildid=' -o binary .\n\nsha256sum binary\n```\n\n## Common Non-Reproducibility Sources\n\n| Source | Fix |\n|--------|-----|\n| Timestamps | Use `SOURCE_DATE_EPOCH` |\n| File paths | Use `-trimpath` |\n| Build IDs | Use `-buildid=` |\n| Random data | Seed deterministically |\n| Parallel builds | Use consistent ordering |\n| Floating dependencies | Pin all versions |\n\n## Setting SOURCE_DATE_EPOCH\n\n```bash\n# Use commit timestamp\nexport SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)\n\n# Or fixed date\nexport SOURCE_DATE_EPOCH=0\n```\n\n## GitHub Actions Example\n\n```yaml\n- name: Reproducible build\n env:\n SOURCE_DATE_EPOCH: ${{ github.event.repository.pushed_at }}\n run: |\n CGO_ENABLED=0 go build \\\n -trimpath \\\n -ldflags='-s -w -buildid=' \\\n -o binary .\n\n # Document checksum\n sha256sum binary >> checksums.txt\n```\n\n## SLSA and Reproducibility\n\nSLSA Level 3 doesn't require reproducibility but helps verify builds:\n\n```yaml\n# Verify SLSA provenance matches source\nslsa-verifier verify-artifact \\\n --provenance-path binary.intoto.jsonl \\\n --source-uri github.com/owner/repo \\\n binary\n```\n\n## Verification Checklist\n\n- [ ] All builds produce identical checksums\n- [ ] Build environment is documented\n- [ ] Dependencies are pinned (lockfiles)\n- [ ] No timestamps embedded in binaries\n- [ ] File paths are stripped\n- [ ] Build process is automated in CI\n- [ ] Checksums are published with releases\n\n## Tools\n\n- **diffoscope** - Detailed diff of binaries\n- **reprotest** - Test reproducibility\n- **buildinfo** - Document build environment\n\n```bash\n# Compare two builds in detail\ndiffoscope binary1 binary2\n\n# Test reproducibility\nreprotest 'go build -o binary .' binary\n```\n\n## References\n\n- [Reproducible Builds](https://reproducible-builds.org/)\n- [Go Reproducible Builds](https://go.dev/blog/rebuild)\n- [SLSA Provenance](https://slsa.dev/provenance)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5572,"content_sha256":"c9d3100107c29a3a55d3ea255a66c7d037d8a13d32c6cadb38e803f50d710a4f"},{"filename":"references/scorecard-playbook.md","content":"# OpenSSF Scorecard Optimization Playbook\n\nProven playbook to raise OpenSSF Scorecard from ~6.8 to ~9.0. Applied successfully on [t3x-rte_ckeditor_image](https://github.com/netresearch/t3x-rte_ckeditor_image).\n\n## Check-by-Check Guide\n\n| Check | Quick Fix | Impact |\n|-------|-----------|--------|\n| Token-Permissions | Move all `write` perms from workflow-level to job-level | 0->10 |\n| Branch-Protection | Set `required_approving_review_count: 1` + auto-approve + protect release branches | 0->8 |\n| Code-Review | Automatic after Branch-Protection fix | 5->10 |\n| Security-Policy | Private vulnerability reporting + coordinated disclosure | 4->10 |\n| Pinned-Dependencies | SHA-pin all actions; use `actions/attest-build-provenance` instead of `slsa-github-generator` | 8->10 |\n| Fuzzing | Not practical for PHP/TYPO3 -- skip | 0 (accept) |\n| CII-Best-Practices | Complete questionnaire at bestpractices.dev | 2->6+ |\n\n## Token-Permissions (0->10)\n\nThe scorecard flags ANY `write` permission at the **workflow-level**. Fix: declare `permissions: contents: read` (or `permissions: {}`) at workflow-level, move all `write` to job-level.\n\n```yaml\n# CORRECT\npermissions:\n contents: read\n\njobs:\n deploy:\n permissions:\n contents: write # only this job gets write\n steps: ...\n\n# WRONG -- Scorecard flags this\npermissions:\n contents: read\n pull-requests: write # write at workflow level!\n```\n\nCommon violators: `pr-quality.yml`, `release-labeler.yml`, `create-release.yml`.\n\n## Branch-Protection (0->8) for Solo Maintainers\n\nSolo-dev projects need `required_approving_review_count >= 1` but can't wait for human reviewers. Solution: auto-approve workflow + ruleset.\n\n**Default branch ruleset:**\n\n```bash\ngh api repos/OWNER/REPO/rulesets/RULESET_ID -X PUT --input - \u003c\u003c'EOF'\n{\n \"rules\": [{\n \"type\": \"pull_request\",\n \"parameters\": {\n \"required_approving_review_count\": 1,\n \"dismiss_stale_reviews_on_push\": true,\n \"require_code_owner_review\": true,\n \"require_last_push_approval\": false,\n \"required_review_thread_resolution\": true\n }\n }]\n}\nEOF\n```\n\n> **`require_last_push_approval` MUST be `false` with merge queues.** The merge queue creates a new merge commit (a new \"push\") which dismisses the existing approval. The auto-approve bot cannot re-approve within the merge queue context, permanently blocking PRs. Verified on [netresearch/ofelia#543](https://github.com/netresearch/ofelia/pull/543).\n\n**Release branch ruleset** (e.g., `TYPO3_*`, `release/*`):\n\n```bash\ngh api repos/OWNER/REPO/rulesets -X POST --input - \u003c\u003c'EOF'\n{\n \"name\": \"release-branches\",\n \"enforcement\": \"active\",\n \"target\": \"branch\",\n \"conditions\": {\"ref_name\": {\"include\": [\"refs/heads/TYPO3_*\"], \"exclude\": []}},\n \"rules\": [\n {\"type\": \"deletion\"},\n {\"type\": \"non_fast_forward\"},\n {\"type\": \"pull_request\", \"parameters\": {\n \"required_approving_review_count\": 1,\n \"dismiss_stale_reviews_on_push\": true,\n \"require_code_owner_review\": true,\n \"require_last_push_approval\": false,\n \"required_review_thread_resolution\": true,\n \"allowed_merge_methods\": [\"merge\"]\n }},\n {\"type\": \"required_status_checks\", \"parameters\": {\n \"required_status_checks\": [{\"context\": \"Build\", \"integration_id\": 15368}],\n \"strict_required_status_checks_policy\": true\n }}\n ]\n}\nEOF\n```\n\nAlso enable `enforce_admins` via legacy API (scorecard reads this separately from rulesets):\n\n```bash\ngh api repos/OWNER/REPO/branches/main/protection/enforce_admins -X POST\n```\n\nPair with `pr-quality.yml` auto-approve workflow (see `github-project` skill) that approves non-fork PRs via `github-actions[bot]`.\n\n**Codeowner review:** `require_code_owner_review: true` works when the CODEOWNERS file lists the solo maintainer (`@username`) or a team they belong to. The auto-approve bot's approval satisfies the \"approved review\" requirement, and the codeowner rule is satisfied because the PR author IS the codeowner. Ensure the CODEOWNERS file exists on the default branch BEFORE enabling this rule.\n\n> **Previous finding (outdated):** We previously reported that `require_code_owner_review` was incompatible with bot auto-approve. This was because the CODEOWNERS file didn't exist on the default branch when the rule was enabled. With CODEOWNERS properly configured, it works. Verified on [netresearch/ofelia](https://github.com/netresearch/ofelia) (2026-03-23).\n\n**Unresolved review threads:** `required_review_thread_resolution: true` will block merge if automated reviewers (Gemini Code Assist, Copilot) leave unresolved threads. Resolve via:\n\n```bash\ngh api graphql -f query='mutation { resolveReviewThread(input: {threadId: \"PRRT_xxx\"}) { thread { isResolved } } }'\n```\n\n**Remaining scorecard warnings (unfixable for solo maintainer):**\n- `required approving review count is 1` -- scorecard wants >= 2, impractical without human reviewers\n\n## Security-Policy (4->10)\n\nThe scorecard requires SECURITY.md with:\n1. Link to private reporting mechanism (not public issues!)\n2. Response timeline (e.g., 48h acknowledgment, 7d fix)\n3. Coordinated disclosure process\n\nTemplate:\n\n```markdown\n## Reporting a Vulnerability\n\n**Please do NOT report security vulnerabilities through public GitHub issues.**\n\nInstead, use [GitHub's private vulnerability reporting](https://github.com/ORG/REPO/security/advisories/new).\n\nWe will acknowledge receipt within 48 hours and aim to provide a fix\nwithin 7 days for critical vulnerabilities.\n\n## Coordinated Disclosure\n\nAfter a fix is released, we will:\n1. Publish a GitHub Security Advisory\n2. Credit the reporter (unless anonymity is requested)\n3. Include the fix in the next release with a CVE identifier if applicable\n```\n\nEnable private vulnerability reporting:\n\n```bash\ngh api repos/OWNER/REPO/private-vulnerability-reporting -X PUT\n```\n\n## SLSA Provenance: Migrate from slsa-github-generator to actions/attest\n\n`slsa-framework/slsa-github-generator` can be SHA-pinned (and should be — see `references/slsa-provenance.md`), but its internal actions use tag refs that conflict with SHA-pinning rulesets ([#4440](https://github.com/slsa-framework/slsa-github-generator/issues/4440)). This triggers Scorecard warnings even when the top-level workflow call is correctly pinned.\n\n**Migration path:** Replace with `actions/attest-build-provenance` (v4.1.0+), fully SHA-pinnable. For SLSA Build Level 3, host the build+attest workflow as a **reusable workflow in the org `.github` repo**. This provides true L3 isolation — callers cannot modify the build process. Verification uses `gh attestation verify` instead of `slsa-verifier`.\n\nThis eliminates the Pinned-Dependencies gap entirely (score can reach 10/10).\n\n## Checks Not Worth Fixing\n\n| Check | Why Skip |\n|-------|----------|\n| Fuzzing (0) | OSS-Fuzz doesn't support PHP/TYPO3; ROI too low |\n| Packaging (-1) | TER publishing isn't detected as a packaging workflow |\n| Signed-Releases (-1) | Detection issue — SLSA provenance exists but isn't recognized |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7010,"content_sha256":"27c98bab42fa01b223824d3963416406487242890da8c2dcacfe7763f93b32c3"},{"filename":"references/security-hardening.md","content":"# Security Hardening Guide\n\n> OpenSSF Gold Badge requirements: `hardening`, `crypto_tls12`, `crypto_used_network`\n>\n> Projects providing network services must use TLS 1.2+ and implement security hardening.\n\n## TLS Configuration\n\n### Minimum TLS 1.2 Requirement\n\n```go\n// Go TLS configuration\nimport (\n \"crypto/tls\"\n \"net/http\"\n)\n\nfunc secureServer() *http.Server {\n tlsConfig := &tls.Config{\n MinVersion: tls.VersionTLS12,\n // Prefer TLS 1.3 when available\n MaxVersion: tls.VersionTLS13,\n // Secure cipher suites only\n CipherSuites: []uint16{\n tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,\n tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,\n tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,\n tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,\n tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\n tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,\n },\n // Note: PreferServerCipherSuites is ignored in Go 1.17+ as the\n // standard library now automatically prefers server cipher suites\n }\n\n return &http.Server{\n Addr: \":443\",\n TLSConfig: tlsConfig,\n }\n}\n```\n\n### Python TLS Configuration\n\n```python\nimport ssl\nimport http.server\n\ndef create_secure_context():\n context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n context.minimum_version = ssl.TLSVersion.TLSv1_2\n context.maximum_version = ssl.TLSVersion.TLSv1_3\n\n # Load certificates\n context.load_cert_chain('server.crt', 'server.key')\n\n # Disable insecure options\n context.options |= ssl.OP_NO_SSLv2\n context.options |= ssl.OP_NO_SSLv3\n context.options |= ssl.OP_NO_TLSv1\n context.options |= ssl.OP_NO_TLSv1_1\n\n return context\n```\n\n### Node.js TLS Configuration\n\n```javascript\nconst https = require('https');\nconst fs = require('fs');\n\nconst options = {\n key: fs.readFileSync('server.key'),\n cert: fs.readFileSync('server.crt'),\n minVersion: 'TLSv1.2',\n maxVersion: 'TLSv1.3',\n ciphers: [\n 'TLS_AES_256_GCM_SHA384',\n 'TLS_CHACHA20_POLY1305_SHA256',\n 'TLS_AES_128_GCM_SHA256',\n 'ECDHE-ECDSA-AES256-GCM-SHA384',\n 'ECDHE-RSA-AES256-GCM-SHA384',\n ].join(':'),\n honorCipherOrder: true,\n};\n\nhttps.createServer(options, handler).listen(443);\n```\n\n## Security Headers\n\n### HTTP Security Headers\n\n```go\n// Go middleware for security headers\nfunc securityHeaders(next http.Handler) http.Handler {\n return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n // Prevent XSS\n w.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n w.Header().Set(\"X-Frame-Options\", \"DENY\")\n w.Header().Set(\"X-XSS-Protection\", \"1; mode=block\")\n\n // Content Security Policy\n w.Header().Set(\"Content-Security-Policy\",\n \"default-src 'self'; script-src 'self'; style-src 'self'\")\n\n // HSTS (HTTPS only)\n w.Header().Set(\"Strict-Transport-Security\",\n \"max-age=31536000; includeSubDomains; preload\")\n\n // Referrer Policy\n w.Header().Set(\"Referrer-Policy\", \"strict-origin-when-cross-origin\")\n\n // Permissions Policy\n w.Header().Set(\"Permissions-Policy\",\n \"geolocation=(), microphone=(), camera=()\")\n\n next.ServeHTTP(w, r)\n })\n}\n```\n\n### Nginx Security Configuration\n\n```nginx\n# /etc/nginx/conf.d/security.conf\n\n# TLS Configuration\nssl_protocols TLSv1.2 TLSv1.3;\nssl_prefer_server_ciphers on;\nssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';\nssl_session_timeout 1d;\nssl_session_cache shared:SSL:50m;\nssl_stapling on;\nssl_stapling_verify on;\n\n# Security Headers\nadd_header X-Content-Type-Options \"nosniff\" always;\nadd_header X-Frame-Options \"DENY\" always;\nadd_header X-XSS-Protection \"1; mode=block\" always;\nadd_header Strict-Transport-Security \"max-age=31536000; includeSubDomains\" always;\nadd_header Content-Security-Policy \"default-src 'self'\" always;\nadd_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n\n# Hide server version\nserver_tokens off;\n```\n\n## Application Hardening\n\n### Input Validation\n\n```go\nimport (\n \"regexp\"\n \"strings\"\n \"unicode/utf8\"\n)\n\n// Validate and sanitize input\nfunc validateInput(input string, maxLen int) (string, error) {\n // Length check\n if utf8.RuneCountInString(input) > maxLen {\n return \"\", fmt.Errorf(\"input exceeds maximum length\")\n }\n\n // Remove null bytes\n input = strings.ReplaceAll(input, \"\\x00\", \"\")\n\n // Validate against allowed pattern\n pattern := regexp.MustCompile(`^[a-zA-Z0-9\\-_\\.]+ enterprise-readiness — Skillopedia )\n if !pattern.MatchString(input) {\n return \"\", fmt.Errorf(\"input contains invalid characters\")\n }\n\n return input, nil\n}\n```\n\n### SQL Injection Prevention\n\n```go\n// ALWAYS use parameterized queries\nfunc getUser(db *sql.DB, userID string) (*User, error) {\n // SAFE - parameterized\n row := db.QueryRow(\"SELECT * FROM users WHERE id = $1\", userID)\n\n // NEVER do this:\n // db.Query(\"SELECT * FROM users WHERE id = \" + userID)\n\n var user User\n err := row.Scan(&user.ID, &user.Name, &user.Email)\n return &user, err\n}\n```\n\n### Command Injection Prevention\n\n```go\nimport \"os/exec\"\n\n// SAFE - use exec.Command with separate arguments\nfunc safeCommand(filename string) error {\n // Arguments are separate - no shell interpretation\n cmd := exec.Command(\"convert\", filename, \"-resize\", \"100x100\", \"output.png\")\n return cmd.Run()\n}\n\n// DANGEROUS - shell interpretation\nfunc unsafeCommand(filename string) error {\n // NEVER do this - shell injection possible\n cmd := exec.Command(\"sh\", \"-c\", \"convert \" + filename + \" output.png\")\n return cmd.Run()\n}\n```\n\n## PHP Secrets Management\n\n### Memory-Safe Secret Handling\n\nAlways clear secrets from memory after use with `sodium_memzero()`:\n\n```php\n// Retrieve and use secret safely\n$secret = $this->vault->retrieve($identifier);\n\ntry {\n $result = $this->processSecret($secret);\n return $result;\n} finally {\n // ALWAYS clear sensitive data from memory\n if ($secret !== null) {\n sodium_memzero($secret);\n }\n}\n```\n\n### Type-Safe Authentication Options\n\nUse backed enums for authentication configuration instead of strings:\n\n```php\nenum SecretPlacement: string\n{\n case Bearer = 'bearer';\n case BasicAuth = 'basic';\n case Header = 'header';\n case QueryParam = 'query';\n case OAuth2 = 'oauth2';\n}\n\n// Type-safe usage - prevents typos and enables IDE completion\npublic function injectAuth(\n array $options,\n SecretPlacement $placement, // Compile-time type safety\n): array {\n return match ($placement) {\n SecretPlacement::Bearer => $this->addBearerAuth($options),\n SecretPlacement::BasicAuth => $this->addBasicAuth($options),\n // match() ensures all cases are handled\n };\n}\n```\n\n### Vault-Integrated HTTP Client Pattern\n\nHTTP clients that use vault secrets should never expose the secret to calling code:\n\n```php\nfinal class VaultHttpClient implements VaultHttpClientInterface\n{\n public function request(string $method, string $url, array $options = []): ResponseInterface\n {\n $secret = null;\n\n try {\n if (isset($options['auth_secret'])) {\n // Retrieve secret just-in-time\n $secret = $this->vault->retrieve($options['auth_secret']);\n\n if ($secret === null) {\n throw new SecretNotFoundException($options['auth_secret']);\n }\n\n // Inject auth - secret never leaves this method\n $options = $this->injectAuthentication($options, $secret);\n }\n\n return $this->httpClient->request($method, $url, $options);\n } finally {\n // Clear secret from memory immediately after use\n if ($secret !== null) {\n sodium_memzero($secret);\n }\n }\n }\n}\n```\n\n### Security Requirements for Secret Handling\n\n- **Clear from memory**: Use `sodium_memzero()` after processing\n- **Never log values**: Only log identifiers, never actual secrets\n- **Audit all access**: Log who accessed what secret and when\n- **Type-safe configuration**: Use enums for auth placement options\n- **Encrypt at rest**: Use envelope encryption (DEK + KEK pattern)\n\n## Dependency Security\n\n### Automated Vulnerability Scanning\n\n```yaml\n# .github/workflows/security.yml\nname: Security Scan\non:\n push:\n branches: [main]\n pull_request:\n schedule:\n - cron: '0 0 * * *'\n\njobs:\n scan:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Run Trivy vulnerability scanner\n uses: aquasecurity/trivy-action@master\n with:\n scan-type: 'fs'\n severity: 'CRITICAL,HIGH'\n exit-code: '1'\n\n - name: Run Snyk\n uses: snyk/actions/golang@master\n env:\n SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n```\n\n### Go Security Scanning\n\n```bash\n# govulncheck - official Go vulnerability scanner\ngo install golang.org/x/vuln/cmd/govulncheck@latest\ngovulncheck ./...\n\n# gosec - static analysis for security\ngo install github.com/securego/gosec/v2/cmd/gosec@latest\ngosec -severity high ./...\n```\n\n## Runtime Hardening\n\n### Container Security\n\n```dockerfile\n# Dockerfile with security best practices\nFROM golang:1.23-alpine AS builder\n\n# Don't run as root\nRUN adduser -D -u 1000 appuser\n\nWORKDIR /app\nCOPY . .\nRUN CGO_ENABLED=0 go build -ldflags='-s -w' -o /app/server\n\nFROM scratch\n# Copy CA certificates for TLS\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/\n# Copy user\nCOPY --from=builder /etc/passwd /etc/passwd\n# Copy binary\nCOPY --from=builder /app/server /server\n\nUSER appuser\nEXPOSE 8080\nENTRYPOINT [\"/server\"]\n```\n\n### Kubernetes Security Context\n\n```yaml\napiVersion: v1\nkind: Pod\nspec:\n securityContext:\n runAsNonRoot: true\n runAsUser: 1000\n fsGroup: 1000\n containers:\n - name: app\n securityContext:\n allowPrivilegeEscalation: false\n readOnlyRootFilesystem: true\n capabilities:\n drop:\n - ALL\n```\n\n## Verification Scripts\n\n### TLS Version Check\n\n```bash\n#!/bin/bash\n# scripts/check-tls.sh\n\nHOST=\"${1:-localhost}\"\nPORT=\"${2:-443}\"\n\necho \"=== TLS Configuration Check ===\"\n\n# Check supported protocols\necho \"Testing TLS versions...\"\nfor version in tls1 tls1_1 tls1_2 tls1_3; do\n if openssl s_client -connect \"$HOST:$PORT\" -\"$version\" \u003c/dev/null 2>/dev/null | grep -q \"CONNECTED\"; then\n echo \" $version: ✅ Supported\"\n else\n echo \" $version: ❌ Not supported\"\n fi\ndone\n\n# Check certificate\necho \"\"\necho \"Certificate info:\"\nopenssl s_client -connect \"$HOST:$PORT\" \u003c/dev/null 2>/dev/null | openssl x509 -noout -dates -subject\n```\n\n### Security Headers Check\n\n```bash\n#!/bin/bash\n# scripts/check-headers.sh\n\nURL=\"${1:-https://localhost}\"\n\necho \"=== Security Headers Check ===\"\n\ncurl -sI \"$URL\" | grep -iE \"(x-content-type|x-frame|x-xss|strict-transport|content-security|referrer-policy)\" | while read -r header; do\n echo \"✅ $header\"\ndone\n```\n\n## Badge Criteria Alignment\n\n| Criterion | Requirement | Implementation |\n|-----------|-------------|----------------|\n| `hardening` | Security hardening applied | Security headers, input validation |\n| `crypto_tls12` | TLS 1.2+ for network services | MinVersion TLS 1.2 config |\n| `crypto_used_network` | Crypto for network data | TLS for all external comms |\n| `crypto_random` | Cryptographic randomness | crypto/rand, not math/rand |\n| `crypto_keylength` | Adequate key lengths | RSA 2048+, ECDSA 256+ |\n\n## Hardening Checklist\n\n### Network Security\n- [ ] TLS 1.2+ only (TLS 1.3 preferred)\n- [ ] Strong cipher suites only\n- [ ] HSTS enabled with long max-age\n- [ ] Certificate pinning for critical connections\n\n### Application Security\n- [ ] All inputs validated and sanitized\n- [ ] Parameterized queries for databases\n- [ ] No shell command interpolation\n- [ ] Security headers on all responses\n\n### Authentication\n- [ ] 2FA required for privileged access\n- [ ] Strong password requirements\n- [ ] Rate limiting on auth endpoints\n- [ ] Secure session management\n\n### Infrastructure\n- [ ] Containers run as non-root\n- [ ] Read-only filesystems where possible\n- [ ] Network policies restrict traffic\n- [ ] Secrets managed securely\n\n## Resources\n\n- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)\n- [Mozilla SSL Configuration Generator](https://ssl-config.mozilla.org/)\n- [CIS Benchmarks](https://www.cisecurity.org/cis-benchmarks)\n- [NIST Cryptographic Standards](https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12572,"content_sha256":"febddd97629f67b0c631912c1448cddcf709035cd912357367543d79f39da1ae"},{"filename":"references/signed-releases.md","content":"# Signed Releases Implementation Guide\n\nCryptographic signing of releases and tags provides verification that artifacts come from trusted sources.\n\n## Two Types of Signing Required\n\nFor **Silver level** OpenSSF Badge:\n1. `signed_releases` - Release artifacts are cryptographically signed\n2. `version_tags_signed` - Git tags are signed\n\n## Part 1: Signed Git Tags\n\n### Setup GPG Key\n\n```bash\n# Generate a GPG key\ngpg --full-generate-key\n\n# List keys to get your key ID\ngpg --list-secret-keys --keyid-format=long\n\n# Export public key for GitHub\ngpg --armor --export YOUR_KEY_ID\n```\n\n### Configure Git\n\n```bash\n# Set signing key\ngit config --global user.signingkey YOUR_KEY_ID\n\n# Enable automatic tag signing\ngit config --global tag.gpgSign true\n\n# Optional: Enable commit signing\ngit config --global commit.gpgSign true\n```\n\n### Create Signed Tags\n\n```bash\n# Create signed tag\ngit tag -s v1.0.0 -m \"Release v1.0.0\"\n\n# Verify a signed tag\ngit tag -v v1.0.0\n\n# Push tags\ngit push origin v1.0.0\n```\n\n### Add GPG Key to GitHub\n\n1. Go to Settings → SSH and GPG keys\n2. Click \"New GPG key\"\n3. Paste your public key\n4. Verify tags will show \"Verified\" badge\n\n## Part 2: Signed Release Artifacts\n\n### Option A: Cosign (Keyless - Recommended)\n\nCosign uses OIDC for keyless signing via Sigstore.\n\n**GitHub Actions Workflow:**\n\n```yaml\nname: Release\n\non:\n push:\n tags: ['v*']\n\npermissions:\n contents: write\n id-token: write # Required for OIDC\n\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Build artifacts\n run: make build\n\n - name: Install Cosign\n uses: sigstore/[email protected]\n\n - name: Sign artifacts\n run: |\n for file in dist/*; do\n cosign sign-blob --yes \\\n --output-certificate \"${file}.pem\" \\\n --output-signature \"${file}.sig\" \\\n \"$file\"\n done\n\n - name: Create release\n uses: softprops/action-gh-release@v1\n with:\n files: |\n dist/*\n dist/*.sig\n dist/*.pem\n```\n\n**Verification Instructions (add to release notes):**\n\n```markdown\n## Verification\n\nInstall cosign: https://docs.sigstore.dev/cosign/installation/\n\nVerify artifacts:\n\\`\\`\\`bash\ncosign verify-blob \\\n --certificate ofelia_linux_amd64.pem \\\n --signature ofelia_linux_amd64.sig \\\n --certificate-identity \"https://github.com/owner/repo/.github/workflows/release.yml@refs/tags/v1.0.0\" \\\n --certificate-oidc-issuer \"https://token.actions.githubusercontent.com\" \\\n ofelia_linux_amd64\n\\`\\`\\`\n```\n\n### Option B: GPG Signing\n\n**GitHub Actions Workflow:**\n\n```yaml\n- name: Import GPG key\n uses: crazy-max/ghaction-import-gpg@v6\n with:\n gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}\n passphrase: ${{ secrets.GPG_PASSPHRASE }}\n\n- name: Sign artifacts\n run: |\n for file in dist/*; do\n gpg --batch --yes --detach-sign --armor \"$file\"\n done\n```\n\n## Part 3: SLSA Provenance (Level 3)\n\nFor highest assurance, add SLSA provenance generation.\n\n### Recommended: GitHub Native Attestations\n\n```yaml\n- name: Attest release artifacts\n uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0\n with:\n subject-path: 'release-assets/*'\n```\n\nThis stores attestations in GitHub's attestation store (not as release assets),\navoiding immutable release conflicts. Verify with `gh attestation verify`.\n\nSee [slsa-provenance.md](slsa-provenance.md) for the full implementation guide.\n\n### Legacy: slsa-github-generator\n\n> **Note**: Prefer native attestations above. The generator uploads `.intoto.jsonl` to\n> releases, which can conflict with GitHub's immutable release policy (March 2026).\n\n```yaml\n# Use SLSA GitHub Generator for Go\nuses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]\nwith:\n go-version-file: go.mod\n upload-assets: true\n```\n\nThis automatically:\n- Builds in isolated environment\n- Generates signed provenance\n- Uploads attestations to release\n\n## Verification Commands\n\n### Verify GPG Signature\n\n```bash\n# Verify detached signature\ngpg --verify file.asc file\n\n# Verify signed tag\ngit tag -v v1.0.0\n```\n\n### Verify Cosign Signature\n\n```bash\ncosign verify-blob \\\n --certificate file.pem \\\n --signature file.sig \\\n --certificate-identity \"IDENTITY\" \\\n --certificate-oidc-issuer \"ISSUER\" \\\n file\n```\n\n### Verify SLSA Provenance (Native Attestations)\n\n```bash\ngh attestation verify artifact.tar.gz --repo owner/repo\n```\n\n### Verify SLSA Provenance (slsa-github-generator)\n\n```bash\nslsa-verifier verify-artifact \\\n --provenance-path file.intoto.jsonl \\\n --source-uri github.com/owner/repo \\\n file\n```\n\n## OpenSSF Badge Criteria\n\n| Criterion | Requirement | Implementation |\n|-----------|-------------|----------------|\n| signed_releases | Artifacts signed | Cosign or GPG |\n| version_tags_signed | Git tags signed | `git tag -s` |\n\n## Best Practices\n\n1. **Prefer keyless signing** (Cosign + OIDC) over traditional keys\n2. **Sign ALL release artifacts** including checksums and SBOMs\n3. **Document verification** in release notes\n4. **Automate in CI** - never sign manually\n5. **Rotate keys** if using traditional GPG\n\n## Common Issues\n\n### Cosign: \"no identity found\"\n- Ensure `id-token: write` permission\n- Verify GitHub OIDC is configured\n\n### GPG: \"secret key not available\"\n- Import key in CI: `gpg --import`\n- Set `GPG_TTY` environment variable\n\n### Git: \"gpg failed to sign the data\"\n- Configure `gpg-agent`\n- Use `export GPG_TTY=$(tty)`\n\n## References\n\n- [Sigstore/Cosign](https://docs.sigstore.dev/cosign/overview/)\n- [SLSA Framework](https://slsa.dev/)\n- [Git Signing Documentation](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)\n- [GitHub Signed Commits](https://docs.github.com/en/authentication/managing-commit-signature-verification)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5839,"content_sha256":"d717c88095af0d36cbcb6f0f3a775c8885ed9b33ca804ef154ec333eb7e1ac0a"},{"filename":"references/slsa-provenance.md","content":"# SLSA Level 3 Provenance Implementation Guide\n\nComplete guide for implementing SLSA Level 3 provenance.\n\n## Overview\n\nSLSA (Supply-chain Levels for Software Artifacts) Level 3 provides:\n- Non-falsifiable provenance\n- Isolated build environment\n- Signed attestations via Sigstore\n- Transparency log entries in Rekor\n\n## Approach 1: GitHub Native Attestations (RECOMMENDED)\n\nGitHub's built-in `actions/attest-build-provenance` is the recommended approach since\n2025. It is simpler to set up, avoids immutable release conflicts, and stores attestations\nin GitHub's attestation store rather than uploading files to the release.\n\n### Why Prefer Native Attestations\n\n- **No release asset conflicts**: Attestations are stored in GitHub's attestation store,\n not uploaded to the release. This avoids the immutable release problem entirely.\n- **Simpler workflow**: Single action call, no base64-subjects format to get wrong.\n- **GitHub-native verification**: `gh attestation verify` works out of the box.\n- **SLSA Level 3 compliant**: Meets the same SLSA v1.0 Build Level 3 requirements.\n\n### Basic Workflow Setup\n\n```yaml\nname: SLSA Provenance\n\non:\n release:\n types: [published]\n\npermissions: {}\n\njobs:\n provenance:\n name: Generate SLSA Provenance\n runs-on: ubuntu-latest\n permissions:\n id-token: write # Required for OIDC signing\n attestations: write # Required to write attestations\n contents: read # Required to read release assets\n steps:\n - name: Harden Runner\n uses: step-security/harden-runner@v2\n with:\n egress-policy: audit\n\n - name: Download release assets\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n run: |\n TAG=\"${{ github.event.release.tag_name }}\"\n mkdir -p release-assets\n for pattern in \"*.tar.gz\" \"*.zip\"; do\n gh release download \"$TAG\" \\\n --repo \"${{ github.repository }}\" \\\n -D release-assets \\\n --pattern \"$pattern\" 2>/dev/null || {\n echo \"::notice::No assets matching pattern '$pattern' found for $TAG\"\n true\n }\n done\n\n - name: Attest release artifacts\n uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0\n with:\n subject-path: 'release-assets/*'\n```\n\n### Trigger Options\n\nYou can use either `release: published` or `workflow_run`:\n\n```yaml\n# Option A: Direct trigger (simpler, recommended for native attestations)\non:\n release:\n types: [published]\n\n# Option B: After another workflow (if assets are built separately)\non:\n workflow_run:\n workflows: [\"Release\"]\n types: [completed]\n```\n\nUnlike slsa-github-generator, native attestations do **not** upload files to the release,\nso there is no race condition with asset uploads — `release: published` is safe.\n\n### Verification\n\nUsers can verify provenance using the GitHub CLI:\n\n```bash\n# Verify a downloaded artifact\ngh attestation verify artifact.tar.gz --repo owner/repo\n\n# Verify with explicit OIDC issuer\ngh attestation verify artifact.tar.gz \\\n --repo owner/repo \\\n --signer-workflow owner/repo/.github/workflows/provenance.yml\n```\n\nAttestations are also visible on the repository's attestations page:\n`https://github.com/OWNER/REPO/attestations`\n\n### Migration from slsa-github-generator\n\nTo migrate an existing workflow:\n\n1. Replace the two-job setup (subjects + generator) with the single-job native approach\n2. Remove `base64-subjects` generation — no longer needed\n3. Change permissions: replace `contents: write` with `attestations: write` + `contents: read`\n4. Remove `compile-generator` and `private-repository` workarounds\n5. Update verification docs: `slsa-verifier` becomes `gh attestation verify`\n\n## Approach 2: slsa-github-generator (LEGACY)\n\n> **WARNING (March 2026)**: GitHub now treats releases as immutable after the first asset\n> upload. The `slsa-github-generator` uploads `multiple.intoto.jsonl` as a release asset,\n> which can fail with `Cannot upload assets to an immutable release` if the release was\n> already finalized by another workflow. **Prefer Approach 1 (native attestations) for\n> new implementations.** The information below remains valid for existing repos still\n> using the generator.\n\n### Basic Workflow Setup\n\n```yaml\nname: SLSA Provenance\n\non:\n workflow_run:\n workflows: [\"Release\"]\n types: [completed]\n workflow_dispatch:\n inputs:\n version:\n description: 'Release version (e.g., v1.0.0)'\n required: true\n type: string\n\npermissions: {}\n\njobs:\n provenance:\n name: Generate SLSA Provenance\n runs-on: ubuntu-latest\n if: github.event.workflow_run.conclusion == 'success'\n permissions:\n actions: read\n id-token: write\n contents: write\n outputs:\n subjects: ${{ steps.subjects.outputs.subjects }}\n has_subjects: ${{ steps.subjects.outputs.has_subjects }}\n version: ${{ steps.version.outputs.version }}\n steps:\n - name: Harden Runner\n uses: step-security/harden-runner@v2\n with:\n egress-policy: audit\n\n - name: Get version\n id: version\n run: |\n if [ \"${{ github.event_name }}\" = \"workflow_dispatch\" ]; then\n VERSION=\"${{ inputs.version }}\"\n else\n VERSION=\"${{ github.event.workflow_run.head_branch }}\"\n fi\n echo \"version=$VERSION\" >> \"$GITHUB_OUTPUT\"\n\n - name: Download release assets\n env:\n GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n run: |\n mkdir -p release-assets\n sleep 5 # Wait for release to be fully available\n VERSION=\"${{ steps.version.outputs.version }}\"\n for pattern in \"*.tar.gz\" \"*.zip\"; do\n gh release download \"$VERSION\" \\\n --repo \"${{ github.repository }}\" \\\n -D release-assets \\\n --pattern \"$pattern\" 2>/dev/null || {\n echo \"::notice::No assets matching pattern '$pattern' found for $VERSION\"\n true\n }\n done\n\n - name: Generate provenance subjects\n id: subjects\n run: |\n cd release-assets\n if ls *.tar.gz *.zip 1> /dev/null 2>&1; then\n # CRITICAL: base64-subjects expects sha256sum format, NOT JSON!\n sha256sum *.tar.gz *.zip > ../provenance-subjects.txt\n SUBJECTS_BASE64=$(cat ../provenance-subjects.txt | base64 -w0)\n echo \"subjects=$SUBJECTS_BASE64\" >> \"$GITHUB_OUTPUT\"\n echo \"has_subjects=true\" >> \"$GITHUB_OUTPUT\"\n else\n echo \"has_subjects=false\" >> \"$GITHUB_OUTPUT\"\n fi\n\n slsa-provenance:\n name: SLSA Level 3 Provenance\n needs: provenance\n if: needs.provenance.outputs.has_subjects == 'true'\n permissions:\n actions: read\n id-token: write\n contents: write\n uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]\n with:\n base64-subjects: ${{ needs.provenance.outputs.subjects }}\n upload-assets: true\n upload-tag-name: ${{ needs.provenance.outputs.version }}\n # Compile from source to avoid binary fetch issues\n compile-generator: true\n # Workaround: generator may incorrectly detect public repo as private\n private-repository: true\n```\n\n## Critical Implementation Gotchas (slsa-github-generator)\n\n### 1. base64-subjects Format (MOST COMMON ERROR)\n\nThe `base64-subjects` input expects **sha256sum raw output format**, NOT JSON.\n\n**WRONG** (will fail with \"unexpected sha256 hash format\"):\n```bash\n# DO NOT DO THIS - creates JSON array\nSUBJECTS=$(cat subjects.txt | awk '{print \"{\\\"name\\\":\\\"\"$2\"\\\",\\\"digest\\\":{\\\"sha256\\\":\\\"\"$1\"\\\"}}\"}' | jq -s -c '.')\nSUBJECTS_BASE64=$(echo \"$SUBJECTS\" | base64 -w0)\n```\n\n**CORRECT**:\n```bash\n# sha256sum format: HASH FILENAME (two spaces between hash and filename)\nsha256sum *.tar.gz *.zip > subjects.txt\nSUBJECTS_BASE64=$(cat subjects.txt | base64 -w0)\n```\n\nExpected format before base64 encoding:\n```\n61d6dcec3d8e7ce6f4c99b07d7a7f5de1237e598eadc15df12c0de968b474f98 myfile.tar.gz\n1bc6c2d8414a725d502398cd5c2970f628c090d1b9644c2b5ae0436e8e3e1d14 myfile.zip\n```\n\n### 2. compile-generator Workaround\n\nIf you see errors like:\n- `exit code 2` during \"Fetching the builder\"\n- `exit code 127` (command not found)\n\nAdd `compile-generator: true` to build from source:\n\n```yaml\nuses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]\nwith:\n compile-generator: true # Builds from source instead of downloading binary\n```\n\n**Trade-off**: Adds ~2 minutes to build time but avoids binary fetch issues.\n\n### 3. private-repository for Public Repos\n\nThe generator may incorrectly detect public repositories as private, showing:\n```\nRepository is private. The workflow has halted in order to keep the repository\nname from being exposed in the public transparency log.\n```\n\n**Solution**: Add `private-repository: true` even for public repos:\n\n```yaml\nuses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]\nwith:\n private-repository: true # Safe for public repos - allows Rekor posting\n```\n\nThis is safe because:\n- For public repos: Just allows posting to Rekor transparency log (which is desired)\n- For private repos: Explicitly acknowledges the repo name will be in public log\n\nNo license or payment required - this is just a safety flag.\n\n### 4. workflow_run Trigger Best Practice\n\nUse `workflow_run` instead of `release: published` to ensure assets are uploaded:\n\n```yaml\non:\n workflow_run:\n workflows: [\"Release\"] # Your release workflow name\n types: [completed]\n```\n\nThis ensures:\n- Release assets are fully uploaded before provenance generation\n- Provenance runs only after successful release\n- Assets are available for download and hashing\n\n## Verification (slsa-github-generator)\n\nUsers can verify provenance with `slsa-verifier`:\n\n```bash\n# Install slsa-verifier\ngo install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest\n\n# Verify artifact against provenance\nslsa-verifier verify-artifact \\\n --provenance-path multiple.intoto.jsonl \\\n --source-uri github.com/owner/repo \\\n artifact.tar.gz\n```\n\nAdd verification instructions to release notes:\n\n```markdown\n## Verification\n\nThis release includes SLSA Level 3 provenance. To verify:\n\n1. Install slsa-verifier: `go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest`\n2. Download the artifact and `multiple.intoto.jsonl`\n3. Run: `slsa-verifier verify-artifact --provenance-path multiple.intoto.jsonl --source-uri github.com/OWNER/REPO artifact.tar.gz`\n```\n\n## Common Errors Reference\n\n| Error | Cause | Solution |\n|-------|-------|----------|\n| `unexpected sha256 hash format` | Using JSON instead of sha256sum format | Use raw sha256sum output |\n| `exit code 2` | Binary download failed | Add `compile-generator: true` |\n| `exit code 127` | Command not found | Add `compile-generator: true` |\n| `Repository is private` | False detection | Add `private-repository: true` |\n| `Input required: path` | Previous step failed | Check provenance subjects step |\n| No `.intoto.jsonl` uploaded | Generator step failed | Check all above issues |\n| `exit code 27` | Final job aggregation failure | Fix upstream generator errors |\n| `Cannot upload assets to an immutable release` | GitHub immutable releases (March 2026) prevent asset upload after finalization | Switch to Approach 1 (native attestations), or use draft releases that are published after provenance upload |\n\n## Output Files\n\nSuccessful provenance generation uploads to the release:\n- `multiple.intoto.jsonl` - SLSA Level 3 provenance attestation\n\nThe `.intoto.jsonl` file contains:\n- **Sigstore certificate** - Fulcio-signed ephemeral certificate\n- **Rekor transparency log entry** - Immutable public record\n- **DSSE envelope** - Dead Simple Signing Envelope with payload\n- **Build provenance** - Builder identity, source URI, environment, parameters\n- **Subject hashes** - SHA256 digests of all attested artifacts\n\n## OpenSSF Scorecard Impact\n\nImplementing SLSA provenance improves these Scorecard checks:\n- **Signed-Releases** - Provenance provides cryptographic signatures\n- **Token-Permissions** - Workflow uses minimal permissions\n- **Pinned-Dependencies** - Generator should be pinned by SHA\n\n## Security Considerations\n\n1. **Pin generator by SHA**: Use full commit SHA, not just version tag\n ```yaml\n uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0\n ```\n\n2. **Minimal permissions**: Only grant `id-token: write` and `contents: write`\n\n3. **Harden runner**: Add `step-security/harden-runner` for egress monitoring\n\n4. **Verify in CI**: Consider adding verification step in downstream consumers\n\n## References\n\n- [SLSA Framework](https://slsa.dev/)\n- [GitHub Artifact Attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds)\n- [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance)\n- [slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) (legacy)\n- [Sigstore](https://www.sigstore.dev/)\n- [Rekor Transparency Log](https://rekor.sigstore.dev/)\n- [slsa-verifier](https://github.com/slsa-framework/slsa-verifier)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13472,"content_sha256":"bb035f26a58ff930dd95c056a439ca0ad3fe02877c794555546030eaf4cbcf97"},{"filename":"references/solo-maintainer-guide.md","content":"# Solo Maintainer Guide for OpenSSF Badge Certification\n\n> Practical guidance for solo maintainers pursuing Silver and Gold level badges.\n\n## Overview\n\nMany OpenSSF Best Practices Badge criteria assume multi-person teams. This guide helps solo\nmaintainers understand which criteria apply, which can be marked N/A with justification, and\nhow to implement compensating controls.\n\n## Criteria Assessment Matrix\n\n### Passing Level (68 criteria)\n\n**Good news:** All Passing level criteria can be met by solo maintainers. Focus on:\n- Documentation (SECURITY.md, README, LICENSE)\n- CI/CD pipeline setup\n- Basic security practices\n\n### Silver Level (55 criteria)\n\n| Criterion | Solo Feasibility | Recommendation |\n|-----------|------------------|----------------|\n| `dco` | ✅ Easy | Sign your own commits |\n| `governance` | ✅ Easy | Document decision-making process |\n| `bus_factor` | ⚠️ N/A | Justify with succession plan |\n| `access_continuity` | ⚠️ N/A | Document backup access |\n| `signed_releases` | ✅ Easy | Use Cosign keyless signing |\n| `version_tags_signed` | ✅ Easy | Use GPG or SSH signing |\n| `test_statement_coverage80` | ✅ Achievable | Invest in comprehensive tests |\n| `automated_integration_testing` | ✅ Achievable | Add integration tests |\n\n### Gold Level (24 criteria)\n\n| Criterion | Solo Feasibility | Recommendation |\n|-----------|------------------|----------------|\n| `two_person_review` | ❌ Structural blocker | Requires organizational change; compensating controls may not suffice |\n| `security_review` | ❌ Structural blocker | Requires formal external audit within last 5 years |\n| `contributors_unassociated` | ⚠️ N/A | Open contribution policy |\n| `bus_factor` | ⚠️ N/A | Enhanced succession planning |\n| `require_2FA` | ⚠️ Depends | Enable on personal account |\n| `test_statement_coverage90` | ⚠️ Hard | Significant test investment |\n| `test_branch_coverage80` | ⚠️ Hard | Advanced coverage analysis |\n| `build_reproducible` | ✅ or N/A | N/A for source-distributed libraries (PHP, Python, npm). Must meet for compiled binaries (Go, Rust). |\n| `security_review` | ✅ Achievable | Self-audit with methodology |\n\n### OSPS Baseline Level 3\n\n| Criterion | Solo Feasibility | Recommendation |\n|-----------|------------------|----------------|\n| `OSPS-QA-07.01` | ❌ Structural blocker | Same as `two_person_review` — requires two-person review |\n| All other Level 3 | ✅ Achievable | SLSA provenance, signed releases, threat model are all solo-achievable |\n\n**Structural Gold Blockers** (cannot be resolved by a solo maintainer alone):\n1. `two_person_review` — needs a second active human reviewer\n2. `security_review` — needs a formal external security audit\n3. `OSPS-QA-07.01` — same requirement as `two_person_review`\n\nThese represent the ceiling for solo maintainer projects (~91% Gold, ~95% Baseline 3).\n\n---\n\n## Compensating Controls\n\n### For `two_person_review`\n\nSince self-approval isn't possible, implement these controls:\n\n```yaml\n# .github/workflows/pr-quality.yml\nname: PR Quality Gates\non: pull_request\n\njobs:\n quality:\n runs-on: ubuntu-latest\n steps:\n # Comprehensive testing replaces human review\n - uses: actions/checkout@v4\n - name: Run full test suite\n run: go test -race -coverprofile=coverage.out ./...\n\n # Static analysis catches issues\n - name: Security scan\n uses: securego/gosec@master\n\n - name: CodeQL analysis\n uses: github/codeql-action/analyze@v3\n\n # PR aging requirement\n - name: Check PR age\n run: |\n CREATED=$(gh pr view ${{ github.event.number }} --json createdAt -q .createdAt)\n AGE_HOURS=$(( ($(date +%s) - $(date -d \"$CREATED\" +%s)) / 3600 ))\n if [ $AGE_HOURS -lt 24 ]; then\n echo \"PR must be at least 24 hours old before merging\"\n exit 1\n fi\n```\n\n### For `bus_factor`\n\nDocument your succession plan:\n\n```markdown\n# Succession Plan\n\n## Immediate Access\n- GitHub repository: Transfer ownership or add backup admin\n- Package registries: Document credentials location (secure)\n- Domain/hosting: Document access procedures\n\n## Knowledge Transfer\n- All decisions documented in ADRs\n- Architecture fully documented\n- No tribal knowledge required\n- Standard tooling used throughout\n\n## Backup Maintainers\nPrimary: [Name] \u003cemail> (confirmed)\nSecondary: Community via GitHub Issues\n\n## Trigger Conditions\n- 90 days of inactivity → Warning to community\n- 180 days of inactivity → Backup maintainer takes over\n```\n\n### For `contributors_unassociated`\n\nActively encourage external contributions:\n\n```markdown\n# In CONTRIBUTING.md\n\n## We Welcome All Contributors!\n\nThis project has no organizational restrictions on contributions. We especially\nwelcome contributions from developers at different organizations to ensure\ndiverse perspectives in the project's development.\n\n### Good First Issues\n\nWe maintain a list of \"good first issue\" tasks to help new contributors get started.\nSee: [Good First Issues](https://github.com/org/repo/labels/good%20first%20issue)\n\n### Contribution Recognition\n\nAll contributors are recognized in our CONTRIBUTORS.md file and release notes.\n```\n\n---\n\n## Self-Audit Process for `security_review`\n\nSolo maintainers can conduct credible security reviews:\n\n### Step 1: Preparation\n\n```bash\n# Run all security tools\ngosec ./...\ngo install golang.org/x/vuln/cmd/govulncheck@latest\ngovulncheck ./...\ntrivy fs --security-checks vuln,config .\ngitleaks detect --source=.\n```\n\n### Step 2: Manual Review Checklist\n\n- [ ] Authentication/authorization flows\n- [ ] Input validation at boundaries\n- [ ] Error handling (no info leakage)\n- [ ] Cryptographic usage (if any)\n- [ ] Secrets management\n- [ ] Logging (no sensitive data)\n- [ ] Dependencies (known vulnerabilities)\n\n### Step 3: Document Findings\n\nUse the `SECURITY_AUDIT.md` template to document your review, including:\n- Methodology used\n- Tools run\n- Findings (even if none)\n- Remediation actions\n- Date and \"auditor\" (yourself)\n\n### Step 4: Schedule Regular Reviews\n\n```yaml\n# .github/workflows/security-review-reminder.yml\nname: Security Review Reminder\non:\n schedule:\n - cron: '0 0 1 */6 *' # Every 6 months\n\njobs:\n remind:\n runs-on: ubuntu-latest\n steps:\n - name: Create issue\n uses: actions/github-script@v7\n with:\n script: |\n github.rest.issues.create({\n owner: context.repo.owner,\n repo: context.repo.repo,\n title: 'Scheduled Security Review Due',\n body: 'Time for the bi-annual security review. See SECURITY_AUDIT.md template.',\n labels: ['security', 'maintenance']\n })\n```\n\n---\n\n## Achievable Goals\n\n### 90% Statement Coverage\n\nWhile challenging, 90% coverage is achievable for solo maintainers:\n\n1. **Start with critical paths** - Ensure main functionality is fully tested\n2. **Test error conditions** - Often missed but easy to add\n3. **Use table-driven tests** - Efficient way to cover many cases\n4. **Mock external dependencies** - Isolate your code for testing\n5. **Review uncovered lines** - Use `go tool cover -html` regularly\n\n### 80% Branch Coverage\n\nRequires more sophisticated testing:\n\n```go\n// Table-driven tests covering all branches\nfunc TestProcess(t *testing.T) {\n tests := []struct {\n name string\n input string\n wantErr bool\n }{\n {\"valid input\", \"good\", false},\n {\"empty input\", \"\", true}, // Error branch\n {\"invalid chars\", \"bad!\", true}, // Validation branch\n {\"too long\", strings.Repeat(\"x\", 1000), true}, // Length check\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n err := Process(tt.input)\n if (err != nil) != tt.wantErr {\n t.Errorf(\"Process() error = %v, wantErr %v\", err, tt.wantErr)\n }\n })\n }\n}\n```\n\n---\n\n## N/A Justification Template\n\nWhen marking criteria as N/A, use this format:\n\n```markdown\n## Criterion: [criterion_name]\n\n**Status:** N/A\n\n**Reason:** [One sentence explanation]\n\n**Compensating Controls:**\n1. [Control 1]\n2. [Control 2]\n3. [Control 3]\n\n**Evidence:** [Link to documentation/code demonstrating controls]\n```\n\n---\n\n## Timeline Expectations\n\n### Passing Level\n- Solo maintainer: 1-2 weeks of focused effort\n- Mostly documentation and basic CI/CD setup\n\n### Silver Level\n- Solo maintainer: 1-2 months\n- Significant testing investment required\n- Documentation formalization needed\n\n### Gold Level\n- Solo maintainer: 3-6 months (or longer)\n- May require community building\n- Some criteria may remain N/A with justification\n- Consider if Gold is necessary for your project\n\n---\n\n## Resources\n\n- [OpenSSF Best Practices Badge](https://www.bestpractices.dev/)\n- [Badge Criteria Details](https://www.bestpractices.dev/en/criteria)\n- [OpenSSF Scorecard](https://securityscorecards.dev/)\n- [Solo Maintainer Support](https://github.com/solo-maintainers)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8991,"content_sha256":"cb8a4f165c3d61e95dc419d4e65f9cf4e92266460cf12efdfb7c87d6a01155b7"},{"filename":"references/sonarcloud.md","content":"# SonarCloud Integration\n\n> Enterprise-grade continuous code quality and security analysis platform\n\n## Overview\n\nSonarCloud is the cloud-hosted version of SonarQube, providing:\n- **Static code analysis** with 5,000+ rules across 30+ languages\n- **Security vulnerability detection** (OWASP Top 10, CWE, SANS Top 25)\n- **Code smell and maintainability tracking**\n- **Test coverage visualization**\n- **Quality gates** for automated enforcement\n- **PR decoration** for immediate feedback\n\n## Pricing\n\n| Tier | Cost | Scope |\n|------|------|-------|\n| **Free** | $0 | Unlimited public repos, full features |\n| **Team** | From $14/mo | Private repos, based on LOC |\n| **Enterprise** | Custom | Advanced features, SSO, support |\n\n**Key**: Free tier provides **full enterprise features** for open-source projects.\n\n## Enterprise Value\n\n### Security Analysis\n\nSonarCloud detects:\n- SQL injection, XSS, command injection\n- Insecure cryptography and hashing\n- Authentication/authorization flaws\n- Sensitive data exposure\n- Security misconfigurations\n- Known vulnerable dependencies\n\n### Compliance Support\n\nAligns with:\n- **OWASP Top 10** - Web application security\n- **CWE/SANS Top 25** - Most dangerous software weaknesses\n- **PCI DSS** - Payment card security\n- **HIPAA** - Healthcare data protection\n\n### Quality Metrics\n\n| Metric | Description | Enterprise Relevance |\n|--------|-------------|---------------------|\n| Reliability | Bug count and severity | System stability |\n| Security | Vulnerabilities and hotspots | Risk management |\n| Maintainability | Code smells and tech debt | Long-term costs |\n| Coverage | Test coverage percentage | Quality assurance |\n| Duplications | Code duplication percentage | Maintainability |\n\n## Setup\n\n### 1. Connect Repository\n\n1. Go to [sonarcloud.io](https://sonarcloud.io)\n2. Sign in with GitHub/GitLab/Bitbucket\n3. Create organization (matches your GitHub org)\n4. Import repository\n\n### 2. Generate Token\n\n1. Account → Security → Generate Token\n2. Save as `SONAR_TOKEN` repository secret\n\n### 3. Add Configuration\n\nCreate `sonar-project.properties` in repository root:\n\n```properties\nsonar.projectKey=org_repo-name\nsonar.organization=your-org\n\n# Source configuration\nsonar.sources=src,Classes\nsonar.tests=tests,Tests\nsonar.exclusions=**/vendor/**,**/node_modules/**,**/*.min.js\n\n# Language-specific\nsonar.php.coverage.reportPaths=coverage.xml\nsonar.php.phpstan.reportPaths=phpstan-report.json\nsonar.javascript.lcov.reportPaths=coverage/lcov.info\nsonar.go.coverage.reportPaths=coverage.out\n\n# Quality settings\nsonar.qualitygate.wait=true\n```\n\n### 4. GitHub Actions Workflow\n\n```yaml\nname: SonarCloud\n\non:\n push:\n branches: [main]\n pull_request:\n types: [opened, synchronize, reopened]\n\njobs:\n sonarcloud:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0 # Full history for accurate blame\n\n - name: Run tests with coverage\n run: |\n # PHP example\n vendor/bin/phpunit --coverage-clover coverage.xml\n # Or Go example\n # go test -coverprofile=coverage.out ./...\n\n - name: SonarCloud Scan\n uses: SonarSource/sonarcloud-github-action@master\n env:\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n```\n\n## Quality Gates\n\n### Default \"Sonar Way\" Gate\n\n| Condition | Threshold | Applies To |\n|-----------|-----------|------------|\n| New bugs | 0 | New code |\n| New vulnerabilities | 0 | New code |\n| New security hotspots reviewed | 100% | New code |\n| New code coverage | ≥80% | New code |\n| New duplicated lines | ≤3% | New code |\n| Maintainability rating | A | New code |\n| Reliability rating | A | New code |\n| Security rating | A | New code |\n\n### Enterprise Custom Gate\n\nFor stricter enterprise requirements:\n\n```\nConditions on New Code:\n- Coverage ≥ 90%\n- Duplicated Lines ≤ 1%\n- Maintainability Rating = A\n- Reliability Rating = A\n- Security Rating = A\n- Security Hotspots Reviewed = 100%\n- New Bugs = 0\n- New Vulnerabilities = 0\n- New Code Smells ≤ 5\n\nConditions on Overall Code:\n- Coverage ≥ 80%\n- Duplicated Lines ≤ 3%\n- Technical Debt Ratio ≤ 5%\n```\n\n## PR Decoration\n\nSonarCloud automatically comments on PRs:\n\n```\n┌────────────────────────────────────────────────────┐\n│ Quality Gate passed │\n├────────────────────────────────────────────────────┤\n│ Coverage: 87.3% (+2.1%) │\n│ │\n│ 0 Bugs (0 new) │\n│ 0 Vulnerabilities (0 new) │\n│ 2 Security Hotspots (1 to review) │\n│ 5 Code Smells (2 new) │\n│ 0.5% Duplication (0.0% new) │\n└────────────────────────────────────────────────────┘\n```\n\n## Security Hotspots\n\nSecurity-sensitive code requiring manual review:\n\n| Category | Examples |\n|----------|----------|\n| **Authentication** | Password handling, session management |\n| **Cryptography** | Encryption, hashing, random number generation |\n| **Injection** | SQL, command, LDAP injection risks |\n| **File handling** | Path traversal, file uploads |\n| **Networking** | HTTP requests, SSL/TLS configuration |\n\n### Hotspot Review Workflow\n\n1. SonarCloud flags security-sensitive code\n2. Developer reviews in SonarCloud UI\n3. Mark as **Safe**, **Fixed**, or **To Review**\n4. Document rationale for **Safe** decisions\n\n## Operational Patterns\n\nDay-to-day workflows for triaging issues, wiring CI, and keeping the project list clean. Token lives in env (`$SONAR_TOKEN`), passed as `Authorization: Bearer $SONAR_TOKEN` (Basic-auth `-u \"$SONAR_TOKEN:\"` also works, but Bearer keeps secrets out of the URL credential slot and avoids tripping secret-scanners on `-u` patterns).\n\n### Quality Gate vs Annotations vs Required Check\n\nThese three concepts are independent — confusing them leads to wrong merge decisions:\n\n- **Quality Gate** (e.g. *0% Coverage on New Code*) — contributes to the `SonarCloud Code Analysis` check status (pass/fail)\n- **Annotations** — individual issues flagged on PR-touched lines, shown inline; do **not** affect the QG by themselves\n- **Required check** — repository ruleset entry (`gh api repos/OWNER/REPO/rulesets/$ID`); only required checks block merge\n\nA PR can sit at `mergeStateStatus: UNSTABLE` (advisory checks failing, still mergeable) versus `BLOCKED` (a required check failing, not mergeable). Verify which checks actually gate merges:\n\n```bash\n# Which checks are required to merge?\ngh api repos/OWNER/REPO/rulesets --jq '.[] | select(.name == \"default\") | .id' \\\n | xargs -I{} gh api repos/OWNER/REPO/rulesets/{} \\\n --jq '.rules[] | select(.type==\"required_status_checks\") | .parameters.required_status_checks[].context'\n# Is \"SonarCloud Code Analysis\" in the output? Usually NOT — it is advisory.\n```\n\nRefactor-PR gotcha: *0% Coverage on New Code* is **structurally unfixable** when the refactored file cannot be loaded by the test runner (e.g. a JS module with CKEditor top-level imports under vitest). Treat the QG fail as advisory rather than chasing coverage on untestable code.\n\n### Bulk Issue Transitions via API\n\nSingle transition (issues):\n\n```bash\ncurl -s -H \"Authorization: Bearer $SONAR_TOKEN\" -X POST \\\n \"https://sonarcloud.io/api/issues/do_transition\" \\\n --data-urlencode \"issue=$KEY\" \\\n --data-urlencode \"transition=wontfix\" # or: falsepositive\n```\n\nAdd a comment (separate call — `do_transition` does not accept `comment`):\n\n```bash\ncurl -s -H \"Authorization: Bearer $SONAR_TOKEN\" -X POST \\\n \"https://sonarcloud.io/api/issues/add_comment\" \\\n --data-urlencode \"issue=$KEY\" \\\n --data-urlencode \"text=Reason: ...\"\n```\n\nAvailable issue transitions: `falsepositive`, `wontfix`, `confirm`, `unconfirm`, `resolve`, `reopen`. Hotspots use a different endpoint:\n\n```bash\ncurl -s -H \"Authorization: Bearer $SONAR_TOKEN\" -X POST \\\n \"https://sonarcloud.io/api/hotspots/change_status\" \\\n --data-urlencode \"hotspot=$KEY\" \\\n --data-urlencode \"status=REVIEWED\" \\\n --data-urlencode \"resolution=SAFE\" # or: FIXED, ACKNOWLEDGED\n```\n\nBulk fetch issue keys for a rule:\n\n```bash\ncurl -s \"https://sonarcloud.io/api/issues/search?componentKeys=$PROJECT&rules=$RULE&issueStatuses=OPEN,CONFIRMED&ps=500\" \\\n | jq -r '.issues[].key'\n```\n\nSequential loops (one transition + one comment per issue) handle ~120 calls without rate-limit pushback — no client-side throttling needed for typical batch sizes.\n\n### Hotspot Triage — Common False-Positive Classes\n\nPatterns that appear repeatedly and can be marked Safe / False Positive without code changes:\n\n| Rule | Pattern | Decision |\n|------|---------|----------|\n| `php:S4790` \"weak hash\" | `substr(md5($url), 0, N)` for cache keys / temp filenames | **Safe** — not crypto, just an identifier |\n| `php:S1313` \"hardcoded IP\" | `'169.254.169.254'` (AWS IMDS) in an SSRF deny-list | **False Positive** — the literal IS the security control |\n| `javascript:S3516` \"always returns same value\" | Returning the same `Promise` object across error and success paths | **False Positive** — Sonar does not model Promise resolution as state change |\n\nTriage flow: read the use-site once, decide, apply via API with an explanatory comment so future reviewers see the rationale.\n\n### Domain-Specific False-Positive Classes\n\n**CKEditor 5 view elements (`javascript:S7761`)** — `getAttribute('data-*')` inside CKE5 upcast/downcast converter callbacks operates on **view elements**, not DOM elements. View elements expose `getAttribute(key)` mirroring the DOM API by name only — they read from an internal attribute map and have **no `.dataset` property**. Refactoring to `.dataset.foo` silently returns `undefined` and drops every `data-*` attribute on upcast. Identify via sibling calls in the same callback: `consumable.consume(viewElement, ...)`, `child.is('element', 'img')`, `figureElement.getChildren()`. Mark all such instances won't-fix in bulk.\n\n**TYPO3 framework signatures (`php:S1172` \"unused parameter\")** — required by TYPO3 contracts even when params are unused; mark won't-fix, do **not** drop:\n\n- `#[AsAllowedCallable]` methods: `(?string $content, array $conf, ServerRequestInterface $request)` — TypoScript USER callback contract\n- DataHandler hooks, e.g. `processDatamap_postProcessFieldArray(string $status, string $table, string $id, array &$fieldArray, DataHandler &$dataHandler)`\n- Other documented hook signatures from TYPO3 core\n\nPrivate methods with unused params are real cleanup — drop the params and update callers (in-file only since they are private).\n\n### Project Hygiene — Archived Repositories\n\nSonarCloud does not auto-prune projects when their GitHub repo is archived. Cross-reference and bulk-delete:\n\n```bash\n# All Sonar projects in the org\ncurl -s \"https://sonarcloud.io/api/components/search_projects?organization=$ORG&ps=500\" \\\n | jq -r '.components[].name' | sort > /tmp/sonar.txt\n\n# All archived GitHub repos (paginate as needed)\ngh api \"orgs/$ORG/repos?type=all&per_page=100&page=1\" \\\n --jq '.[] | select(.archived) | .name' \\\n | sort > /tmp/archived.txt\n\n# Delete each archived-but-still-in-Sonar project; expect HTTP 204 per delete\ncomm -12 /tmp/sonar.txt /tmp/archived.txt \\\n | while read -r repo; do\n curl -s -H \"Authorization: Bearer $SONAR_TOKEN\" -X POST \\\n \"https://sonarcloud.io/api/projects/delete?project=${ORG}_${repo}\" \\\n -w \"$repo: %{http_code}\\n\"\n done\n```\n\nReal-world result on the `netresearch` org (2026-05): 270 → 198 projects after pruning 72 archived entries.\n\n### CI Wiring Gotchas\n\n**Language-agnostic shared workflows cannot carry coverage.** A reusable workflow such as `org/.github/.github/workflows/sonarqube.yml` is intentionally generic — checkout + scan only, no test step. Repos that want coverage must inline the scan in their own workflow:\n\n```yaml\nsonarqube:\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n - name: Setup runtime + run tests with coverage\n run: |\n # PHP example\n vendor/bin/phpunit --coverage-clover .Build/logs/clover-unit.xml\n # JS example\n npm ci && npm run test:coverage\n - uses: SonarSource/sonarqube-scan-action@59db25f34e16620e48ab4bb9e4a5dce155cb5432 # v8.0.0 — pin to 40-char SHA, comment with version; or use sonarcloud-github-action\n```\n\nWire the resulting reports in `sonar-project.properties`:\n\n```properties\nsonar.php.coverage.reportPaths=.Build/logs/clover-unit.xml\nsonar.javascript.lcov.reportPaths=Tests/JavaScript/coverage/lcov.info\n```\n\n**Vitest cross-directory coverage requires `allowExternal`.** Production code at `../../Resources/...` from `Tests/JavaScript/` produces an empty `lcov.info` despite tests passing, because v8 silently drops files outside the workspace boundary regardless of include globs:\n\n```js\n// vitest.config.js\nexport default {\n test: {\n coverage: {\n provider: 'v8',\n allowExternal: true, // required for cross-dir source under test\n include: ['../../Resources/Public/JavaScript/**'],\n reportsDirectory: './coverage',\n reporter: ['lcov', 'text'],\n },\n },\n}\n```\n\n**Test fixtures duplicating production trigger CPD false positives.** When test files mirror inline production callbacks (e.g. CKE5 upcast/downcast lambdas that cannot be imported in isolation), Sonar's CPD flags the mirrors as duplication. Mitigate with a targeted exclusion:\n\n```properties\nsonar.cpd.exclusions=Tests/JavaScript/tests/**\n```\n\nThis is a legitimate exclusion — the duplication is a testing-pattern necessity, not maintenance debt.\n\n## Integration with Existing Tools\n\n### PHPStan Integration\n\nExport PHPStan results to SonarCloud:\n\n```bash\nvendor/bin/phpstan analyze --error-format=json > phpstan-report.json\n```\n\n```properties\n# sonar-project.properties\nsonar.php.phpstan.reportPaths=phpstan-report.json\n```\n\n### ESLint Integration\n\n```bash\nnpx eslint --format json --output-file eslint-report.json .\n```\n\n```properties\nsonar.eslint.reportPaths=eslint-report.json\n```\n\n### Coverage Integration\n\n| Language | Coverage Tool | Property |\n|----------|--------------|----------|\n| PHP | PHPUnit | `sonar.php.coverage.reportPaths` |\n| JavaScript | Istanbul/NYC | `sonar.javascript.lcov.reportPaths` |\n| Go | go test | `sonar.go.coverage.reportPaths` |\n| Python | Coverage.py | `sonar.python.coverage.reportPaths` |\n| Java | JaCoCo | `sonar.coverage.jacoco.xmlReportPaths` |\n\n## Badges\n\nAdd to README for visibility:\n\n```markdown\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=org_repo&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=org_repo)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=org_repo&metric=coverage)](https://sonarcloud.io/summary/new_code?id=org_repo)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=org_repo&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=org_repo)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=org_repo&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=org_repo)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=org_repo&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=org_repo)\n```\n\n## OpenSSF Badge Alignment\n\nSonarCloud helps satisfy OpenSSF Best Practices criteria:\n\n| Criterion | SonarCloud Feature |\n|-----------|-------------------|\n| `static_analysis` | ✅ Core feature |\n| `static_analysis_fixed` | ✅ Quality Gate enforcement |\n| `dynamic_analysis` | ⚠️ Partial (security hotspots) |\n| `test_statement_coverage80` | ✅ Coverage tracking |\n| `test_branch_coverage80` | ✅ Branch coverage (Gold) |\n| `warnings_fixed` | ✅ Code smell tracking |\n\n## Enterprise Best Practices\n\n### 1. Baseline for Legacy Code\n\nFor existing projects with technical debt:\n\n```properties\n# Focus quality gate on new code only\nsonar.leak.period=previous_version\n```\n\n### 2. Branch Analysis\n\n```properties\n# Analyze feature branches\nsonar.branch.name=${BRANCH_NAME}\nsonar.branch.target=main\n```\n\n### 3. Monorepo Support\n\n```properties\n# Multiple projects in one repo\nsonar.projectKey=org_monorepo_service-a\nsonar.projectBaseDir=services/service-a\n```\n\n### 4. Pull Request Analysis\n\n```yaml\n# Only analyze PRs to main/develop\non:\n pull_request:\n branches: [main, develop]\n```\n\n### 5. Scheduled Full Analysis\n\n```yaml\n# Weekly full analysis\non:\n schedule:\n - cron: '0 2 * * 0' # Sunday 2 AM\n```\n\n## Comparison with Alternatives\n\n| Feature | SonarCloud | CodeClimate | Codacy | GitHub CodeQL |\n|---------|------------|-------------|--------|---------------|\n| Free for OSS | ✅ Full | ✅ Full | ⚠️ Limited | ✅ Full |\n| Security rules | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |\n| PHP support | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |\n| Coverage | ✅ | ✅ | ✅ | ❌ |\n| Quality gates | ✅ | ✅ | ✅ | ❌ |\n| PR decoration | ✅ | ✅ | ✅ | ✅ |\n| Self-hosted | SonarQube | ❌ | ❌ | ✅ |\n\n## Resources\n\n- [SonarCloud Documentation](https://docs.sonarcloud.io/)\n- [Security Rules](https://rules.sonarsource.com/)\n- [Quality Gates](https://docs.sonarcloud.io/improving/quality-gates/)\n- [GitHub Actions Integration](https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/github-actions-for-sonarcloud/)\n- [SonarQube (Self-hosted)](https://www.sonarqube.org/)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17886,"content_sha256":"b21a24637dbbdd7748e8a05f02260914a8d91616f13cc9b3266e94745afe41d5"},{"filename":"references/test-invocation.md","content":"# Test Invocation Guide\n\n> OpenSSF Gold Badge requirements: `test_invocation`, `test_continuous_integration`,\n> `test_statement_coverage90`, `test_branch_coverage80`, `dynamic_analysis_enable_assertions`\n>\n> Projects must have standard test invocation, CI integration, and meet coverage thresholds.\n\n## Standard Test Invocation\n\n### Go Projects\n\n```bash\n# Standard invocation (Gold requirement: test_invocation)\ngo test ./...\n\n# With coverage (Gold: 90% statement coverage)\ngo test -coverprofile=coverage.out -covermode=atomic ./...\n\n# View coverage\ngo tool cover -func=coverage.out\ngo tool cover -html=coverage.out -o coverage.html\n\n# With race detection\ngo test -race ./...\n\n# Verbose with coverage\ngo test -v -coverprofile=coverage.out ./...\n```\n\n### Python Projects\n\n```bash\n# Standard invocation\npytest\n\n# With coverage (Gold: 90% statement, 80% branch)\npytest --cov=. --cov-report=term --cov-report=html --cov-branch\n\n# With assertions enabled (always enabled in pytest by default)\npython -O -m pytest # Optimized mode DISABLES assertions - don't use\n\n# Verbose with coverage\npytest -v --cov=. --cov-fail-under=90\n```\n\n### Node.js Projects\n\n```bash\n# Standard invocation\nnpm test\n\n# With coverage\nnpm test -- --coverage --coverageThreshold='{\"global\":{\"statements\":90,\"branches\":80}}'\n\n# Or with Jest directly\njest --coverage --coverageReporters=text --coverageReporters=html\n```\n\n### Rust Projects\n\n```bash\n# Standard invocation\ncargo test\n\n# With coverage (requires cargo-llvm-cov)\ncargo llvm-cov --html\n\n# With assertions (debug builds have assertions by default)\ncargo test # assertions enabled\ncargo test --release # some assertions disabled - use debug_assert! for release\n```\n\n## CI Integration\n\n### GitHub Actions Test Workflow\n\n```yaml\n# .github/workflows/test.yml\nname: Test\n\non:\n push:\n branches: [main]\n pull_request:\n\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Set up Go\n uses: actions/setup-go@v5\n with:\n go-version-file: go.mod\n\n - name: Run tests with coverage\n run: |\n go test -v -race -coverprofile=coverage.out -covermode=atomic ./...\n\n - name: Check coverage threshold\n run: |\n COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%')\n echo \"Total coverage: ${COVERAGE}%\"\n\n # Gold level: 90% statement coverage\n if (( $(echo \"$COVERAGE \u003c 90\" | bc -l) )); then\n echo \"❌ Coverage ${COVERAGE}% is below 90% threshold\"\n exit 1\n fi\n echo \"✅ Coverage meets Gold level threshold\"\n\n - name: Upload coverage\n uses: codecov/codecov-action@v4\n with:\n files: coverage.out\n```\n\n### Branch Coverage for Gold Level\n\n```yaml\n# Python with branch coverage\n- name: Run tests with branch coverage\n run: |\n pytest --cov=. --cov-branch --cov-report=term --cov-fail-under=90\n\n # Check branch coverage specifically\n BRANCH_COV=$(coverage report --include=\"*.py\" | grep TOTAL | awk '{print $NF}' | tr -d '%')\n if (( $(echo \"$BRANCH_COV \u003c 80\" | bc -l) )); then\n echo \"❌ Branch coverage ${BRANCH_COV}% is below 80% threshold\"\n exit 1\n fi\n```\n\n## PHPUnit Version Compatibility in CI\n\n### Problem: Attributes Differ Between Major Versions\n\nPHPUnit 12 introduced new attributes (e.g., `#[AllowMockObjectsWithoutExpectations]`)\nthat do not exist in PHPUnit 11. When a CI matrix tests multiple PHP versions, each\nPHP version may resolve a different PHPUnit major version via Composer constraints.\n\n**Example failure:**\n```\nPHP 8.2 + PHPUnit 11: #[AllowMockObjectsWithoutExpectations] → \"Unknown attribute\"\nPHP 8.4 + PHPUnit 12: works fine\n```\n\n### Best Practices\n\n| Practice | Description |\n|----------|-------------|\n| **Test with lowest PHP version** | Always include the minimum supported PHP in the CI matrix |\n| **Pin PHPUnit major range** | Use `\"phpunit/phpunit\": \"^11.0\"` not `\"^11.0 \\|\\| ^12.0\"` unless you actively support both |\n| **Guard version-specific attributes** | Use `#[RequiresPhpunit('12')]` or conditional logic |\n| **CI matrix awareness** | Know which PHPUnit version each PHP version pulls in |\n\n### CI Matrix Example (PHP)\n\n```yaml\nstrategy:\n matrix:\n php-version: ['8.2', '8.3', '8.4']\n\nsteps:\n - name: Install dependencies\n run: composer install --no-progress\n\n - name: Show PHPUnit version\n run: .Build/bin/phpunit --version # Log which version is active\n\n - name: Run tests\n run: .Build/bin/phpunit -c Build/phpunit/UnitTests.xml\n```\n\n### Version-Safe Test Code\n\n```php\n// Instead of using PHPUnit 12-only attributes unconditionally:\n// #[AllowMockObjectsWithoutExpectations] // Breaks on PHPUnit 11\n\n// Option 1: Use setUp() method (works across versions)\nprotected function setUp(): void\n{\n $this->mock = $this->createMock(SomeClass::class);\n $this->mock->expects(self::any())->method('someMethod');\n}\n\n// Option 2: Conditionally apply based on PHPUnit version\n// Only use version-specific attributes when you control the PHPUnit constraint\n```\n\n## Assertions\n\n### Go Assertions\n\n```go\n// Go doesn't have built-in assertions, but you can:\n\n// 1. Use testing assertions\nfunc TestSomething(t *testing.T) {\n result := compute()\n if result != expected {\n t.Fatalf(\"expected %v, got %v\", expected, result)\n }\n}\n\n// 2. Use panic for invariants (always enabled)\nfunc process(data []byte) {\n if len(data) == 0 {\n panic(\"invariant violation: empty data\")\n }\n // ...\n}\n\n// 3. Use testify for rich assertions\nimport \"github.com/stretchr/testify/assert\"\n\nfunc TestSomething(t *testing.T) {\n assert.Equal(t, expected, actual)\n assert.NoError(t, err)\n assert.NotNil(t, result)\n}\n```\n\n### Python Assertions\n\n```python\n# Python assertions are enabled by default\n# They're disabled with python -O (optimize) flag\n\ndef process(data):\n assert data is not None, \"data cannot be None\"\n assert len(data) > 0, \"data cannot be empty\"\n # ...\n\n# pytest assertions\ndef test_something():\n result = compute()\n assert result == expected\n assert isinstance(result, MyClass)\n```\n\n### Rust Assertions\n\n```rust\n// debug_assert! - only in debug builds\nfn process(data: &[u8]) {\n debug_assert!(!data.is_empty(), \"data cannot be empty\");\n // ...\n}\n\n// assert! - always enabled\nfn critical_process(data: &[u8]) {\n assert!(!data.is_empty(), \"data cannot be empty\");\n // ...\n}\n\n// Tests use assert! macros\n#[test]\nfn test_something() {\n let result = compute();\n assert_eq!(result, expected);\n assert!(result.is_valid());\n}\n```\n\n## Coverage Thresholds\n\n### Coverage Configuration Files\n\n**Go (no config file, use scripts):**\n```bash\n#!/bin/bash\n# scripts/check-coverage.sh\n\nTHRESHOLD=\"${1:-90}\"\nCOVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%')\n\nif (( $(echo \"$COVERAGE \u003c $THRESHOLD\" | bc -l) )); then\n echo \"Coverage $COVERAGE% \u003c $THRESHOLD%\"\n exit 1\nfi\n```\n\n**Python (pyproject.toml):**\n```toml\n[tool.coverage.run]\nbranch = true\nsource = [\".\"]\n\n[tool.coverage.report]\nfail_under = 90\nexclude_lines = [\n \"pragma: no cover\",\n \"def __repr__\",\n \"raise NotImplementedError\",\n]\n```\n\n**Node.js (jest.config.js):**\n```javascript\nmodule.exports = {\n coverageThreshold: {\n global: {\n statements: 90,\n branches: 80,\n functions: 90,\n lines: 90,\n },\n },\n};\n```\n\n## Multi-Level Coverage Script\n\n```bash\n#!/bin/bash\n# scripts/check-coverage-level.sh\n# Usage: ./check-coverage-level.sh [passing|silver|gold]\n\nLEVEL=\"${1:-passing}\"\nCOVERAGE_FILE=\"${2:-coverage.out}\"\n\ncase \"$LEVEL\" in\n passing)\n STATEMENT_THRESHOLD=60\n BRANCH_THRESHOLD=0 # Not required\n ;;\n silver)\n STATEMENT_THRESHOLD=80\n BRANCH_THRESHOLD=0 # Not required\n ;;\n gold)\n STATEMENT_THRESHOLD=90\n BRANCH_THRESHOLD=80\n ;;\n *)\n echo \"Usage: $0 [passing|silver|gold]\"\n exit 1\n ;;\nesac\n\necho \"=== Coverage Check: $LEVEL Level ===\"\necho \"Statement threshold: ${STATEMENT_THRESHOLD}%\"\necho \"Branch threshold: ${BRANCH_THRESHOLD}%\"\n\n# Extract statement coverage\nif [[ -f \"$COVERAGE_FILE\" ]]; then\n STATEMENT_COV=$(go tool cover -func=\"$COVERAGE_FILE\" | grep total | awk '{print $3}' | tr -d '%')\n echo \"Statement coverage: ${STATEMENT_COV}%\"\n\n if (( $(echo \"$STATEMENT_COV \u003c $STATEMENT_THRESHOLD\" | bc -l) )); then\n echo \"❌ Statement coverage below ${STATEMENT_THRESHOLD}%\"\n exit 1\n fi\n echo \"✅ Statement coverage meets $LEVEL threshold\"\nfi\n\n# For Python, also check branch coverage\nif [[ \"$LEVEL\" == \"gold\" && -f \".coverage\" ]]; then\n BRANCH_COV=$(coverage report --show-missing | grep TOTAL | awk '{print $(NF-1)}' | tr -d '%')\n echo \"Branch coverage: ${BRANCH_COV}%\"\n\n if (( $(echo \"$BRANCH_COV \u003c $BRANCH_THRESHOLD\" | bc -l) )); then\n echo \"❌ Branch coverage below ${BRANCH_THRESHOLD}%\"\n exit 1\n fi\n echo \"✅ Branch coverage meets Gold threshold\"\nfi\n\necho \"\"\necho \"✅ All coverage thresholds met for $LEVEL level\"\n```\n\n## Documentation Requirements\n\nAdd to README.md:\n\n```markdown\n## Testing\n\n### Running Tests\n\n\\`\\`\\`bash\n# Standard test invocation\ngo test ./...\n\n# With coverage\ngo test -coverprofile=coverage.out ./...\ngo tool cover -func=coverage.out\n\\`\\`\\`\n\n### Coverage Requirements\n\n| Level | Statement | Branch |\n|-------|-----------|--------|\n| Passing | 60% | - |\n| Silver | 80% | - |\n| Gold | 90% | 80% |\n\nCurrent coverage: ![Coverage](https://codecov.io/gh/ORG/REPO/branch/main/graph/badge.svg)\n```\n\n## Badge Criteria Alignment\n\n| Criterion | Requirement | Implementation |\n|-----------|-------------|----------------|\n| `test_invocation` | Standard test command | `go test`, `pytest`, `npm test` |\n| `test_continuous_integration` | Tests run in CI | GitHub Actions workflow |\n| `test_statement_coverage90` | 90% statement coverage | Coverage threshold check |\n| `test_branch_coverage80` | 80% branch coverage | Branch coverage enabled |\n| `dynamic_analysis_enable_assertions` | Assertions enabled | Default in test/debug mode |\n\n## Resources\n\n- [Go Testing](https://go.dev/doc/tutorial/add-a-test)\n- [pytest Documentation](https://docs.pytest.org/)\n- [Jest Coverage](https://jestjs.io/docs/cli#--coverage)\n- [OpenSSF Testing Criteria](https://www.bestpractices.dev/en/criteria#1.test)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10372,"content_sha256":"d5912721cf846461ea0ab8fbb93f4c6783bcb9f3981d5785b6aba7f3c16cffbb"},{"filename":"references/tier-framing.md","content":"# Tier framing — when does this skill apply, and to what depth?\n\nThis skill is the **high-stakes end** of the verification spectrum. Lower-stakes projects (internal scripts, prototypes, throwaway PoCs) have correspondingly lighter bars — they do not need SLSA Level 3, cosign attestation, or OpenSSF Silver. Production deploys, customer-facing releases, and supply-chain-critical artifacts do.\n\n## Ask before applying\n\nIf the project being assessed does not yet ship to customers or production-grade infrastructure, ask the user **what target tier they want** before applying the full checklist. Applying enterprise-grade controls to a prototype is wasted work — and worse, it trains the team to ignore the report. That is the same failure mode the [`automated-assessment` skill](https://github.com/netresearch/automated-assessment-skill/blob/main/skills/automated-assessment/references/calibration.md) calls *calibration debt*: a noisy report nobody acts on costs CI time AND erodes trust in the next genuine signal.\n\n## What \"tier\" means here\n\nRoughly:\n\n| Tier | What it ships to | What this skill applies |\n|------|------------------|-------------------------|\n| Prototype / internal script | A laptop, a one-off CI job | Almost nothing here. Skip. |\n| Internal tool / shared utility | Internal teams, not customers | Subset: Critical Rules, basic dependabot, CI permissions hardening. |\n| Customer-facing service | Production, paying customers | Full checklist applies. |\n| Supply-chain-critical artifact | Other projects depend on releases | Full checklist + SLSA L3 + signed releases + Best Practices Silver+. |\n\nThese are heuristics, not strict gates. Surface the tier question; let the user decide.\n\n## Why the checklist is the floor, not the ceiling\n\nFor projects in the customer-facing or supply-chain-critical tiers, the checklists in this skill define the **floor** of production readiness — not the ceiling. A project meeting every checklist item is the minimum bar to ship enterprise-grade; it does not exempt the project from project-specific risks (sector compliance, internal threat models, customer SLAs) that the checklist cannot encode.\n\nThe checklist is also not a default to apply to every repo in the org. Applying it indiscriminately confuses \"this is a high-stakes project\" with \"this skill ran on it\" and dilutes the signal.\n\n## Inspiration\n\nThe tier framing comes from [Spotify Engineering — Better Experiments with LLM Evals: A Funnel, Not a Fork](https://engineering.atspotify.com/2026/5/better-experiments-with-llm-evals-a-funnel-not-a-fork): *\"Not every change needs the same evidence: quick directional tests for iteration and data gathering, rigorous tests for ship decisions.\"* The same is true here — not every project needs SLSA L3.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2781,"content_sha256":"bf0a3d34b0b3f399b4b71d9003955e04602e560cd17ebd165d1b8860e4782d3a"},{"filename":"scripts/add-spdx-headers.sh","content":"#!/bin/bash\n# add-spdx-headers.sh - Add SPDX license headers to source files\n# Usage: ./add-spdx-headers.sh [--license MIT|Apache-2.0|...] [--copyright \"Your Name\"]\nset -euo pipefail\n\nLICENSE=\"${1:-MIT}\"\nCOPYRIGHT=\"${2:-$(git config user.name 2>/dev/null || echo \"Project Authors\")}\"\nYEAR=$(date +%Y)\n\necho \"=== Adding SPDX Headers ===\"\necho \"License: $LICENSE\"\necho \"Copyright: $COPYRIGHT\"\necho \"Year: $YEAR\"\necho \"\"\n\n# Go files\nadd_go_header() {\n local file=\"$1\"\n if ! grep -q \"SPDX-License-Identifier\" \"$file\"; then\n local temp_file\n temp_file=$(mktemp)\n {\n echo \"// SPDX-License-Identifier: $LICENSE\"\n echo \"// Copyright (c) $YEAR $COPYRIGHT\"\n echo \"\"\n cat \"$file\"\n } > \"$temp_file\" && cat \"$temp_file\" > \"$file\" && rm -f \"$temp_file\" || rm -f \"$temp_file\"\n echo \"Added header to: $file\"\n fi\n}\n\n# Python files\nadd_python_header() {\n local file=\"$1\"\n if ! grep -q \"SPDX-License-Identifier\" \"$file\"; then\n local temp_file\n temp_file=$(mktemp)\n # Handle shebang\n if head -1 \"$file\" | grep -q \"^#!\"; then\n {\n head -1 \"$file\"\n echo \"# SPDX-License-Identifier: $LICENSE\"\n echo \"# Copyright (c) $YEAR $COPYRIGHT\"\n echo \"\"\n tail -n +2 \"$file\"\n } > \"$temp_file\" && cat \"$temp_file\" > \"$file\" && rm -f \"$temp_file\" || rm -f \"$temp_file\"\n else\n {\n echo \"# SPDX-License-Identifier: $LICENSE\"\n echo \"# Copyright (c) $YEAR $COPYRIGHT\"\n echo \"\"\n cat \"$file\"\n } > \"$temp_file\" && cat \"$temp_file\" > \"$file\" && rm -f \"$temp_file\" || rm -f \"$temp_file\"\n fi\n echo \"Added header to: $file\"\n fi\n}\n\n# JavaScript/TypeScript files\nadd_js_header() {\n local file=\"$1\"\n if ! grep -q \"SPDX-License-Identifier\" \"$file\"; then\n local temp_file\n temp_file=$(mktemp)\n {\n echo \"// SPDX-License-Identifier: $LICENSE\"\n echo \"// Copyright (c) $YEAR $COPYRIGHT\"\n echo \"\"\n cat \"$file\"\n } > \"$temp_file\" && cat \"$temp_file\" > \"$file\" && rm -f \"$temp_file\" || rm -f \"$temp_file\"\n echo \"Added header to: $file\"\n fi\n}\n\n# Find and process files\necho \"Processing Go files...\"\nfind . -name \"*.go\" -not -path \"./vendor/*\" -not -path \"./.git/*\" | while read -r file; do\n add_go_header \"$file\"\ndone\n\necho \"\"\necho \"Processing Python files...\"\nfind . -name \"*.py\" -not -path \"./vendor/*\" -not -path \"./.git/*\" -not -path \"./.venv/*\" | while read -r file; do\n add_python_header \"$file\"\ndone\n\necho \"\"\necho \"Processing JavaScript/TypeScript files...\"\nfind . \\( -name \"*.js\" -o -name \"*.ts\" -o -name \"*.jsx\" -o -name \"*.tsx\" \\) \\\n -not -path \"./node_modules/*\" -not -path \"./.git/*\" -not -path \"./dist/*\" | while read -r file; do\n add_js_header \"$file\"\ndone\n\necho \"\"\necho \"Done! Verify changes with: git diff\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2984,"content_sha256":"26fc9cd300f1ba5d93b1b0aad5860c7c0888cb44ff9c35f94671ac7033702835"},{"filename":"scripts/analyze-bus-factor.sh","content":"#!/bin/bash\n# analyze-bus-factor.sh - Analyze commit distribution for bus factor assessment\n# Usage: ./analyze-bus-factor.sh [--days 365] [--threshold 2]\n# OpenSSF Badge Criteria: bus_factor (Silver/Gold)\nset -euo pipefail\n\nDAYS=\"${1:-365}\"\nTHRESHOLD=\"${2:-2}\"\n\necho \"=== Bus Factor Analysis ===\"\necho \"Period: Last $DAYS days\"\necho \"Target: Bus factor >= $THRESHOLD\"\necho \"\"\n\n# Check if we're in a git repository\nif ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then\n echo \"Error: Not a git repository\"\n exit 1\nfi\n\n# Get commit counts by author\necho \"=== Commit Distribution ===\"\necho \"\"\necho \"Author Commits %\"\necho \"---------------------------------------- -------- ----\"\n\n# Calculate total commits first\nTOTAL_COMMITS=$(git log --since=\"$DAYS days ago\" --format=\"%ae\" 2>/dev/null | wc -l | tr -d ' ')\n\nif [ \"$TOTAL_COMMITS\" -eq 0 ]; then\n echo \"No commits found in the last $DAYS days\"\n echo \"\"\n echo \"Bus Factor: 0 (no activity)\"\n exit 1\nfi\n\n# Get unique authors with commit counts, sorted by count descending\ngit log --since=\"$DAYS days ago\" --format=\"%aN \u003c%aE>\" 2>/dev/null | \\\n sort | uniq -c | sort -rn | head -20 | while read -r count author; do\n # Calculate percentage using awk (POSIX-compatible, no bc dependency)\n PCT=$(awk -v c=\"$count\" -v t=\"$TOTAL_COMMITS\" 'BEGIN { printf \"%.1f\", (c/t)*100 }')\n printf \"%-40s %8d %4s%%\\n\" \"$author\" \"$count\" \"$PCT\"\ndone\n\necho \"\"\necho \"Total commits: $TOTAL_COMMITS\"\necho \"\"\n\n# Calculate bus factor\n# Bus factor = number of authors needed to cover 50% of commits\necho \"=== Bus Factor Calculation ===\"\necho \"\"\n\nCUMULATIVE=0\nBUS_FACTOR=0\nHALF_COMMITS=$((TOTAL_COMMITS / 2))\nRESULT=\"\"\n\n# Process authors in order of commit count\n# Use process substitution to keep loop in main shell (fixes subshell exit issue)\nwhile read -r count author; do\n CUMULATIVE=$((CUMULATIVE + count))\n BUS_FACTOR=$((BUS_FACTOR + 1))\n\n if [ \"$CUMULATIVE\" -ge \"$HALF_COMMITS\" ]; then\n RESULT=\"found\"\n echo \"Authors needed for 50% of commits: $BUS_FACTOR\"\n\n if [ \"$BUS_FACTOR\" -ge \"$THRESHOLD\" ]; then\n echo \"\"\n echo \"✓ Bus factor ($BUS_FACTOR) meets threshold ($THRESHOLD)\"\n exit 0\n else\n echo \"\"\n echo \"✗ Bus factor ($BUS_FACTOR) below threshold ($THRESHOLD)\"\n echo \"\"\n echo \"Recommendations:\"\n echo \"1. Recruit additional maintainers\"\n echo \"2. Document critical code paths for knowledge transfer\"\n echo \"3. Pair programming to spread knowledge\"\n exit 1\n fi\n fi\ndone \u003c \u003c(git log --since=\"$DAYS days ago\" --format=\"%aN\" 2>/dev/null | sort | uniq -c | sort -rn)\n\n# If we get here with only one author or no commits matched threshold\nif [ -z \"$RESULT\" ]; then\n echo \"\"\n echo \"✗ Single maintainer project - bus factor = 1\"\n echo \"\"\n echo \"Recommendations for solo maintainers:\"\n echo \"1. Document architecture and decision rationale\"\n echo \"2. Create comprehensive onboarding documentation\"\n echo \"3. Consider recruiting co-maintainers\"\n echo \"4. Mark bus_factor as N/A with justification in badge application\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3224,"content_sha256":"26a99ded9409bc6461a6d846f70f43bdadb1d1d536010f250586c537d4a00634"},{"filename":"scripts/check-branch-coverage.sh","content":"#!/bin/bash\n# check-branch-coverage.sh - Check branch (decision) coverage for Go projects\n# Usage: ./check-branch-coverage.sh [--threshold 80] [--package ./...]\n# OpenSSF Badge Criteria: test_branch_coverage80 (Gold)\nset -euo pipefail\n\nTHRESHOLD=\"80\"\nPACKAGE=\"./...\"\n\n# Parse arguments\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --threshold)\n THRESHOLD=\"$2\"\n shift 2\n ;;\n --package)\n PACKAGE=\"$2\"\n shift 2\n ;;\n *)\n # Support positional args for backwards compatibility\n if [ -z \"${THRESHOLD_SET:-}\" ]; then\n THRESHOLD=\"$1\"\n THRESHOLD_SET=1\n else\n PACKAGE=\"$1\"\n fi\n shift\n ;;\n esac\ndone\n\nCOVERAGE_FILE=\"coverage.out\"\n\necho \"=== Branch Coverage Analysis ===\"\necho \"Threshold: $THRESHOLD%\"\necho \"Package: $PACKAGE\"\necho \"\"\n\n# Check for Go\nif ! command -v go >/dev/null 2>&1; then\n echo \"Error: Go is not installed\"\n exit 1\nfi\n\n# Run tests with coverage\necho \"Running tests with coverage...\"\ngo test -coverprofile=\"$COVERAGE_FILE\" -covermode=atomic \"$PACKAGE\" 2>/dev/null || {\n echo \"Error: Tests failed\"\n exit 1\n}\n\nif [ ! -f \"$COVERAGE_FILE\" ]; then\n echo \"Error: Coverage file not generated\"\n exit 1\nfi\n\necho \"\"\necho \"=== Statement Coverage ===\"\nSTMT_COVERAGE=$(go tool cover -func=\"$COVERAGE_FILE\" | grep total | awk '{print $3}' | tr -d '%')\necho \"Statement coverage: $STMT_COVERAGE%\"\n\necho \"\"\necho \"=== Branch Coverage Analysis ===\"\necho \"\"\necho \"Note: Go's native coverage tool measures statement coverage, not branch coverage.\"\necho \"For true branch coverage, use one of these approaches:\"\necho \"\"\n\n# Method 1: Estimate from statement coverage\n# Branch coverage is typically 70-85% of statement coverage\nESTIMATED_BRANCH=$(awk -v s=\"$STMT_COVERAGE\" 'BEGIN { printf \"%.1f\", s * 0.8 }')\necho \"1. Estimated branch coverage (conservative): ~$ESTIMATED_BRANCH%\"\necho \" (Statement coverage * 0.8 as rough estimate)\"\necho \"\"\n\n# Method 2: Use gocov for more detailed analysis\necho \"2. For accurate branch coverage, install gocov-cobertura:\"\necho \" go install github.com/boumenot/gocover-cobertura@latest\"\necho \" go tool cover -xml=coverage.out > coverage.xml\"\necho \" # Then analyze with coverage tools that support branch metrics\"\necho \"\"\n\n# Method 3: Check for conditional complexity\necho \"3. Analyzing conditional complexity in codebase...\"\n\n# Count if/else and switch statements (Go has no ternary operator)\nIF_COUNT=$(grep -rn \"if \" --include=\"*.go\" . 2>/dev/null | grep -v \"_test.go\" | grep -v \"vendor/\" | wc -l | tr -d ' ')\nSWITCH_COUNT=$(grep -rn \"switch \" --include=\"*.go\" . 2>/dev/null | grep -v \"_test.go\" | grep -v \"vendor/\" | wc -l | tr -d ' ')\nSELECT_COUNT=$(grep -rn \"select {\" --include=\"*.go\" . 2>/dev/null | grep -v \"_test.go\" | grep -v \"vendor/\" | wc -l | tr -d ' ')\n\necho \" Conditional statements in source code:\"\necho \" - if statements: $IF_COUNT\"\necho \" - switch statements: $SWITCH_COUNT\"\necho \" - select statements: $SELECT_COUNT\"\necho \" Total decision points: $((IF_COUNT + SWITCH_COUNT + SELECT_COUNT))\"\necho \"\"\n\n# Provide assessment\necho \"=== Assessment ===\"\necho \"\"\n\n# Use awk for floating point comparison (POSIX-compatible)\nif awk -v e=\"$ESTIMATED_BRANCH\" -v t=\"$THRESHOLD\" 'BEGIN { exit !(e >= t) }'; then\n echo \"✓ Estimated branch coverage ($ESTIMATED_BRANCH%) meets threshold ($THRESHOLD%)\"\n echo \"\"\n echo \"Recommendation: Validate with proper branch coverage tools for Gold badge\"\nelse\n echo \"✗ Estimated branch coverage ($ESTIMATED_BRANCH%) is below threshold ($THRESHOLD%)\"\n echo \"\"\n echo \"To improve branch coverage:\"\n echo \"1. Add tests for error paths and edge cases\"\n echo \"2. Test both branches of if/else statements\"\n echo \"3. Cover all switch cases including default\"\n echo \"4. Test boundary conditions\"\nfi\n\necho \"\"\necho \"=== Detailed Coverage Report ===\"\necho \"\"\necho \"Low coverage files:\"\ngo tool cover -func=\"$COVERAGE_FILE\" | awk '$3 != \"100.0%\" && $3 != \"\" {print}' | sort -t

Enterprise Readiness Assessment Production/enterprise tier only — see . When to Use - Production/enterprise readiness evaluations - Supply chain security: SLSA provenance, cosign signing, SBOMs - CI/CD hardening, workflow permissions - OpenSSF Best Practices (Passing/Silver/Gold), OSPS Baseline (L1/2/3) - Scorecard optimization (Token-Permissions, Branch-Protection, Pinned-Deps) - Code review, ADRs, changelogs, SECURITY.md Assessment Workflow 1. Discovery : Identify platform, languages, existing CI/CD, dependabot.yml 2. Scoring : Apply checklists; check Scorecard, badge criteria, coverage 3.…

\\t' -k3 -n | head -10\n\necho \"\"\necho \"Files with 0% coverage:\"\ngo tool cover -func=\"$COVERAGE_FILE\" | awk '$3 == \"0.0%\" {print $1}' | head -10\n\n# Exit based on estimated coverage\nif awk -v e=\"$ESTIMATED_BRANCH\" -v t=\"$THRESHOLD\" 'BEGIN { exit !(e >= t) }'; then\n exit 0\nelse\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":4381,"content_sha256":"dc6910e59af5c5373a52fe01633ae72d16008cb162249749fdc232af3e15e65e"},{"filename":"scripts/check-coverage-threshold.sh","content":"#!/bin/bash\n# check-coverage-threshold.sh - Validate test coverage meets requirements\n# Usage: ./check-coverage-threshold.sh [--threshold 80] [--coverage-file coverage.out]\nset -euo pipefail\n\nTHRESHOLD=\"${1:-80}\"\nCOVERAGE_FILE=\"${2:-coverage.out}\"\n\necho \"=== Coverage Threshold Check ===\"\necho \"Required: ${THRESHOLD}%\"\necho \"Coverage file: $COVERAGE_FILE\"\necho \"\"\n\nif [[ ! -f \"$COVERAGE_FILE\" ]]; then\n echo \"Error: Coverage file not found: $COVERAGE_FILE\"\n echo \"\"\n echo \"Generate coverage with:\"\n echo \" Go: go test -coverprofile=coverage.out ./...\"\n echo \" Python: pytest --cov=. --cov-report=term\"\n echo \" Node: npm test -- --coverage\"\n exit 1\nfi\n\n# Detect coverage format and extract percentage\nif head -1 \"$COVERAGE_FILE\" | grep -q \"^mode:\"; then\n # Go coverage format\n COVERAGE=$(go tool cover -func=\"$COVERAGE_FILE\" 2>/dev/null | grep total | awk '{print $3}' | tr -d '%')\n echo \"Format: Go coverage profile\"\nelif grep -q \"TOTAL\" \"$COVERAGE_FILE\"; then\n # Python coverage format\n COVERAGE=$(grep \"TOTAL\" \"$COVERAGE_FILE\" | awk '{print $NF}' | tr -d '%')\n echo \"Format: Python coverage report\"\nelse\n # Try to extract any percentage (POSIX-compatible, works on macOS and Linux)\n COVERAGE=$(grep -E '[0-9]+\\.?[0-9]*%' \"$COVERAGE_FILE\" 2>/dev/null | tail -1 | sed 's/.*[^0-9]\\([0-9][0-9]*\\.[0-9]*\\)%.*/\\1/' | sed 's/.*[^0-9]\\([0-9][0-9]*\\)%.*/\\1/' | head -1)\n echo \"Format: Generic\"\nfi\n\nif [[ -z \"$COVERAGE\" ]]; then\n echo \"Error: Could not extract coverage percentage\"\n exit 1\nfi\n\necho \"Coverage: ${COVERAGE}%\"\necho \"\"\n\n# Compare using awk for floating point (POSIX-compatible, no bc dependency)\nif awk -v cov=\"$COVERAGE\" -v thresh=\"$THRESHOLD\" 'BEGIN {exit !(cov >= thresh)}' 2>/dev/null; then\n echo \"✓ Coverage ${COVERAGE}% meets threshold ${THRESHOLD}%\"\n exit 0\nelse\n echo \"✗ Coverage ${COVERAGE}% is below threshold ${THRESHOLD}%\"\n echo \"\"\n echo \"To increase coverage:\"\n echo \"1. Run: go test -coverprofile=coverage.out ./...\"\n echo \"2. View: go tool cover -html=coverage.out\"\n echo \"3. Add tests for uncovered code paths\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2132,"content_sha256":"08556074cdfceedf54bcfd7fa48468d35c99bd29f9b33918886c7bc42740dbfe"},{"filename":"scripts/check-tls-minimum.sh","content":"#!/bin/bash\n# check-tls-minimum.sh - Verify TLS 1.2+ minimum version is enforced in code\n# Usage: ./check-tls-minimum.sh [directory]\n# OpenSSF Badge Criteria: crypto_tls12 (Silver), crypto_used_network (Gold)\nset -euo pipefail\n\nTARGET_DIR=\"${1:-.}\"\n\necho \"=== TLS Minimum Version Check ===\"\necho \"Directory: $TARGET_DIR\"\necho \"\"\n\n# Track findings\nISSUES=0\nGOOD=0\nWARNINGS=0\n\n# Patterns that indicate TLS configuration\ndeclare -a TLS_PATTERNS\n\necho \"=== Checking Go Files ===\"\necho \"\"\n\n# Go: Check for MinVersion configuration\nGO_FILES=$(find \"$TARGET_DIR\" -name \"*.go\" ! -path \"*/vendor/*\" ! -path \"*_test.go\" 2>/dev/null || echo \"\")\n\nif [ -n \"$GO_FILES\" ]; then\n # Check for proper TLS 1.2+ enforcement\n while IFS= read -r file; do\n [ -z \"$file\" ] && continue\n\n # Check for TLS configuration\n if grep -q \"tls\\.Config\" \"$file\" 2>/dev/null; then\n # Check if MinVersion is set to TLS 1.2 or higher\n if grep -E \"MinVersion.*tls\\.VersionTLS1[234]\" \"$file\" >/dev/null 2>&1; then\n echo \"✓ $file: TLS 1.2+ minimum configured\"\n GOOD=$((GOOD + 1))\n elif grep -E \"MinVersion.*tls\\.VersionTLS1[01]\" \"$file\" >/dev/null 2>&1; then\n echo \"✗ $file: Insecure TLS version (TLS 1.0 or 1.1)\"\n ISSUES=$((ISSUES + 1))\n elif grep -q \"InsecureSkipVerify.*true\" \"$file\" 2>/dev/null; then\n echo \"⚠ $file: InsecureSkipVerify enabled (certificate validation disabled)\"\n WARNINGS=$((WARNINGS + 1))\n else\n echo \"⚠ $file: TLS config found but MinVersion not explicitly set\"\n WARNINGS=$((WARNINGS + 1))\n fi\n fi\n done \u003c\u003c\u003c \"$GO_FILES\"\nfi\n\necho \"\"\necho \"=== Checking Python Files ===\"\necho \"\"\n\n# Python: Check for SSL context configuration\nPY_FILES=$(find \"$TARGET_DIR\" -name \"*.py\" ! -path \"*/venv/*\" ! -path \"*/.venv/*\" 2>/dev/null || echo \"\")\n\nif [ -n \"$PY_FILES\" ]; then\n while IFS= read -r file; do\n [ -z \"$file\" ] && continue\n\n if grep -q \"ssl\\.\" \"$file\" 2>/dev/null; then\n # Check for proper TLS version\n if grep -E \"PROTOCOL_TLS|TLSVersion\\.TLSv1_2|TLSVersion\\.TLSv1_3\" \"$file\" >/dev/null 2>&1; then\n echo \"✓ $file: Modern TLS protocol configured\"\n GOOD=$((GOOD + 1))\n elif grep -E \"PROTOCOL_SSLv[23]|PROTOCOL_TLSv1$|PROTOCOL_TLSv1_1\" \"$file\" >/dev/null 2>&1; then\n echo \"✗ $file: Deprecated protocol version\"\n ISSUES=$((ISSUES + 1))\n elif grep -q \"verify_mode.*CERT_NONE\" \"$file\" 2>/dev/null; then\n echo \"⚠ $file: Certificate verification disabled\"\n WARNINGS=$((WARNINGS + 1))\n fi\n fi\n done \u003c\u003c\u003c \"$PY_FILES\"\nfi\n\necho \"\"\necho \"=== Checking JavaScript/TypeScript Files ===\"\necho \"\"\n\n# Node.js: Check for HTTPS/TLS configuration\nJS_FILES=$(find \"$TARGET_DIR\" \\( -name \"*.js\" -o -name \"*.ts\" \\) ! -path \"*/node_modules/*\" 2>/dev/null || echo \"\")\n\nif [ -n \"$JS_FILES\" ]; then\n while IFS= read -r file; do\n [ -z \"$file\" ] && continue\n\n if grep -E \"https\\.|tls\\.\" \"$file\" 2>/dev/null; then\n if grep -q \"rejectUnauthorized.*false\" \"$file\" 2>/dev/null; then\n echo \"⚠ $file: Certificate verification disabled\"\n WARNINGS=$((WARNINGS + 1))\n elif grep -E \"minVersion.*TLSv1\\.[23]\" \"$file\" >/dev/null 2>&1; then\n echo \"✓ $file: TLS 1.2+ minimum configured\"\n GOOD=$((GOOD + 1))\n elif grep -E \"secureProtocol.*TLSv1_method|SSLv\" \"$file\" >/dev/null 2>&1; then\n echo \"✗ $file: Deprecated protocol\"\n ISSUES=$((ISSUES + 1))\n fi\n fi\n done \u003c\u003c\u003c \"$JS_FILES\"\nfi\n\necho \"\"\necho \"=== Checking Configuration Files ===\"\necho \"\"\n\n# Check common config files\nCONFIG_FILES=$(find \"$TARGET_DIR\" \\( -name \"*.yml\" -o -name \"*.yaml\" -o -name \"*.json\" -o -name \"*.toml\" \\) \\\n ! -path \"*/node_modules/*\" ! -path \"*/vendor/*\" 2>/dev/null | head -50 || echo \"\")\n\nif [ -n \"$CONFIG_FILES\" ]; then\n while IFS= read -r file; do\n [ -z \"$file\" ] && continue\n\n # Check for TLS/SSL configurations in config files\n if grep -qi \"ssl\\|tls\" \"$file\" 2>/dev/null; then\n if grep -Ei \"tls_version.*1\\.[01]|ssl_version.*[23]|min.*version.*1\\.[01]\" \"$file\" >/dev/null 2>&1; then\n echo \"✗ $file: Deprecated TLS/SSL version in config\"\n ISSUES=$((ISSUES + 1))\n elif grep -Ei \"verify.*false|insecure.*true\" \"$file\" >/dev/null 2>&1; then\n echo \"⚠ $file: Certificate verification may be disabled\"\n WARNINGS=$((WARNINGS + 1))\n fi\n fi\n done \u003c\u003c\u003c \"$CONFIG_FILES\"\nfi\n\necho \"\"\necho \"=== Summary ===\"\necho \"\"\necho \"Secure configurations: $GOOD\"\necho \"Issues (insecure): $ISSUES\"\necho \"Warnings: $WARNINGS\"\necho \"\"\n\nif [ \"$ISSUES\" -gt 0 ]; then\n echo \"✗ TLS security issues found\"\n echo \"\"\n echo \"Recommendations:\"\n echo \"1. Set minimum TLS version to 1.2 or higher\"\n echo \"2. Enable certificate verification\"\n echo \"3. Use modern cipher suites\"\n echo \"\"\n echo \"Go example:\"\n echo \" tlsConfig := &tls.Config{\"\n echo \" MinVersion: tls.VersionTLS12,\"\n echo \" }\"\n echo \"\"\n echo \"OpenSSF Badge: crypto_tls12 = Unmet\"\n exit 1\nelif [ \"$WARNINGS\" -gt 0 ]; then\n echo \"⚠ TLS configuration warnings (review recommended)\"\n echo \"\"\n echo \"OpenSSF Badge: crypto_tls12 = Likely Met (verify configurations)\"\n exit 0\nelif [ \"$GOOD\" -eq 0 ]; then\n echo \"No TLS configurations found to check.\"\n echo \"\"\n echo \"If this project uses network communication:\"\n echo \"- Ensure TLS 1.2+ is enforced\"\n echo \"- Enable certificate verification\"\n echo \"\"\n echo \"OpenSSF Badge: crypto_tls12 = N/A (or needs manual review)\"\n exit 0\nelse\n echo \"✓ All TLS configurations meet security requirements\"\n echo \"\"\n echo \"OpenSSF Badge: crypto_tls12 = Met\"\n exit 0\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":6043,"content_sha256":"9cd7d643b885b70854c15e5c32cd43410cb977259e799aa6bd0785167bb2f3c6"},{"filename":"scripts/submit-badges.py","content":"#!/usr/bin/env python3\n\"\"\"Submit OpenSSF Best Practices Badge data for multiple projects.\n\nThis script automates submitting badge criteria data to bestpractices.dev\nusing session cookie authentication and CSRF-protected form submissions.\n\nKey features:\n- Multi-project support with per-level badge data files\n- Proper session cookie handling via CookieJar (rotation-safe)\n- Lock version extraction with dual attribute-order regex\n- NoRedirectHandler to preserve cookie rotation on 302\n- Rate limiting between submissions\n\nUsage:\n # Single project/level\n python3 submit-badges.py PROJECT_ID [LEVEL] [DATA_FILE]\n\n # All configured projects\n python3 submit-badges.py --all\n\nAuthentication:\n Set BADGE_COOKIE env var (preferred) or put cookie in /tmp/badge-cookie.txt\n Cookie value is the _BadgeApp_session from browser DevTools.\n Write to file recommended (avoids shell quoting issues with +/=/chars).\n\n SECURITY WARNING: Session cookies are sensitive credentials.\n - NEVER hardcode cookies in source code or commit them to version control.\n - ALWAYS use the BADGE_COOKIE environment variable for automation.\n - The cookie file (/tmp/badge-cookie.txt) should have restrictive permissions (0600).\n - Cookies are ephemeral and rotate on each server response; treat as secrets.\n\nVerification (use /projects/ NOT /en/projects/ for fresh data):\n curl -s \"https://www.bestpractices.dev/projects/ID.json?_=$(date +%s)\" | \\\\\n jq '{badge_level, badge_percentage_0, badge_percentage_1, badge_percentage_2}'\n\"\"\"\n\nimport json\nimport os\nimport re\nimport sys\nimport time\nimport http.cookiejar\nimport urllib.parse\nimport urllib.request\n\nCOOKIE_NAME = \"_BadgeApp_session\"\nBASE_URL = \"https://www.bestpractices.dev\"\n\nAUTH_TOKEN_PATTERN = re.compile(\n r'\u003cinput type=\"hidden\" name=\"authenticity_token\" value=\"([^\"]+)\"'\n)\nCSRF_TOKEN_PATTERN = re.compile(r'\u003cmeta name=\"csrf-token\" content=\"([^\"]+)\"')\n# Handle both attribute orderings: name before value AND value before name\nLOCK_VERSION_PATTERN = re.compile(\n r'(?:name=\"project\\[lock_version\\]\"[^>]*value=\"([^\"]*)\"|'\n r'value=\"(\\d+)\"[^>]*name=\"project\\[lock_version\\]\")'\n)\n\n# Fields that are auto-detected and CANNOT be submitted\nAUTO_DETECTED_FIELDS = {\n \"homepage_url_status\",\n \"homepage_url_justification\",\n \"report_url_status\",\n \"report_url_justification\",\n}\n\n\nclass NoRedirectHandler(urllib.request.HTTPErrorProcessor):\n \"\"\"Prevents following redirects so we can capture Set-Cookie from 302 responses.\"\"\"\n\n def http_response(self, request, response):\n return response\n\n https_response = http_response\n\n\ndef make_opener(cookie):\n \"\"\"Create urllib opener with cookie jar and no-redirect handling.\"\"\"\n cj = http.cookiejar.CookieJar()\n c = http.cookiejar.Cookie(\n version=0,\n name=COOKIE_NAME,\n value=cookie,\n port=None,\n port_specified=False,\n domain=\"www.bestpractices.dev\",\n domain_specified=True,\n domain_initial_dot=False,\n path=\"/\",\n path_specified=True,\n secure=True,\n expires=None,\n discard=True,\n comment=None,\n comment_url=None,\n rest={},\n rfc2109=False,\n )\n cj.set_cookie(c)\n return urllib.request.build_opener(\n urllib.request.HTTPCookieProcessor(cj),\n NoRedirectHandler,\n ), cj\n\n\ndef get_edit_page(opener, project_id, level=\"passing\"):\n \"\"\"Fetch the edit page and extract CSRF tokens + lock version.\n\n IMPORTANT: Level is required in the URL path for ALL levels including passing.\n Use /en/projects/{id}/passing/edit, NOT /en/projects/{id}/edit.\n \"\"\"\n url = f\"{BASE_URL}/en/projects/{project_id}/{level}/edit\"\n\n req = urllib.request.Request(url, method=\"GET\")\n resp = opener.open(req)\n code = resp.getcode()\n\n if code in (301, 302, 303):\n print(f\" WARNING: Got redirect {code} on edit page - cookie may be expired\")\n return None, None, None\n\n html = resp.read().decode(\"utf-8\", errors=\"replace\")\n\n auth_match = AUTH_TOKEN_PATTERN.search(html)\n csrf_match = CSRF_TOKEN_PATTERN.search(html)\n lock_match = LOCK_VERSION_PATTERN.search(html)\n\n auth_token = auth_match.group(1) if auth_match else None\n csrf_token = csrf_match.group(1) if csrf_match else None\n # Handle both regex groups (one for each attribute ordering)\n lock_version = (lock_match.group(1) or lock_match.group(2)) if lock_match else None\n\n return auth_token, csrf_token, lock_version\n\n\ndef check_insufficient_criteria(opener, project_id, level=\"silver\"):\n \"\"\"Check which criteria the platform considers insufficient for the badge.\n\n Uses the _enough img indicators on the edit page.\n Returns list of (criterion_name, alt_text) tuples that are NOT sufficient.\n \"\"\"\n url = f\"{BASE_URL}/en/projects/{project_id}/{level}/edit\"\n req = urllib.request.Request(url, method=\"GET\")\n resp = opener.open(req)\n\n if resp.getcode() in (301, 302, 303):\n return []\n\n html = resp.read().decode(\"utf-8\", errors=\"replace\")\n all_enough = re.findall(r'\u003cimg[^>]*id=\"(\\w+)_enough\"[^>]*alt=\"([^\"]+)\"', html)\n return [\n (name, alt) for name, alt in all_enough if \"not\" in alt.lower() or \"Not\" in alt\n ]\n\n\ndef submit_data(opener, project_id, level, data, auth_token, lock_version):\n \"\"\"Submit badge data via PATCH.\"\"\"\n url = f\"{BASE_URL}/en/projects/{project_id}/{level}\"\n\n # Build form data\n form_data = {\"_method\": \"patch\", \"authenticity_token\": auth_token}\n if lock_version:\n form_data[\"project[lock_version]\"] = lock_version\n\n for key, value in data.items():\n if key in AUTO_DETECTED_FIELDS:\n continue\n form_data[f\"project[{key}]\"] = value\n\n encoded = urllib.parse.urlencode(form_data).encode(\"utf-8\")\n req = urllib.request.Request(url, data=encoded, method=\"POST\")\n req.add_header(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n resp = opener.open(req)\n code = resp.getcode()\n\n body = resp.read().decode(\"utf-8\", errors=\"replace\")\n\n if code in (200,):\n if \"form contains\" in body.lower() and \"error\" in body.lower():\n errors = re.findall(r\"\u003cli>(.*?)\u003c/li>\", body)\n print(f\" FORM ERRORS: {errors[:5]}\")\n return False\n print(f\" Status: {code} (form re-rendered, may have validation issues)\")\n return True\n elif code in (301, 302, 303):\n print(f\" Status: {code} (redirect = success)\")\n return True\n else:\n print(f\" Status: {code} (unexpected)\")\n return False\n\n\ndef submit_level(opener, project_id, level, data_file):\n \"\"\"Submit a single level's badge data.\"\"\"\n print(f\"\\n{'=' * 60}\")\n print(f\"Project {project_id} - Level: {level}\")\n print(f\"Data file: {data_file}\")\n print(f\"{'=' * 60}\")\n\n if not os.path.exists(data_file):\n print(\" SKIP: Data file not found\")\n return False\n\n with open(data_file) as f:\n data = json.load(f)\n\n print(f\" Criteria count: {len(data)}\")\n\n # Step 1: Get edit page for CSRF tokens + lock version\n print(\" Fetching edit page...\")\n auth_token, csrf_token, lock_version = get_edit_page(opener, project_id, level)\n\n if not auth_token:\n print(\" ERROR: Could not get auth token - cookie expired?\")\n return False\n\n print(f\" Auth token: {auth_token[:20]}...\")\n print(f\" Lock version: {lock_version}\")\n\n if not lock_version:\n print(\" WARNING: No lock_version found - submission may silently fail!\")\n\n # Step 2: Submit data\n print(f\" Submitting {len(data)} criteria...\")\n result = submit_data(opener, project_id, level, data, auth_token, lock_version)\n\n return result\n\n\n# Example project configurations (customize for your projects)\nPROJECTS = {\n # 'project-name': {\n # 'id': 12345,\n # 'levels': {\n # 'passing': '/path/to/badge-data-passing.json',\n # 'silver': '/path/to/badge-data-silver.json',\n # 'gold': '/path/to/badge-data-gold.json',\n # }\n # },\n}\n\n\ndef main():\n # SECURITY: Prefer environment variable for cookie to avoid file-based risks.\n cookie = os.environ.get(\"BADGE_COOKIE\", \"\")\n if not cookie:\n cookie_file = \"/tmp/badge-cookie.txt\"\n if os.path.exists(cookie_file):\n # Warn if cookie file has overly permissive permissions\n file_mode = oct(os.stat(cookie_file).st_mode & 0o777)\n if file_mode != \"0o600\":\n print(\n f\"WARNING: {cookie_file} has permissions {file_mode} \"\n f\"(expected 0o600). Run: chmod 600 {cookie_file}\"\n )\n with open(cookie_file) as f:\n cookie = f.read().strip()\n if not cookie:\n print(\n \"ERROR: Set BADGE_COOKIE env var or put cookie in /tmp/badge-cookie.txt\"\n )\n sys.exit(1)\n\n opener, cj = make_opener(cookie)\n\n if len(sys.argv) >= 2 and sys.argv[1] == \"--all\":\n for name, config in PROJECTS.items():\n for level, data_file in config[\"levels\"].items():\n if os.path.exists(data_file):\n submit_level(opener, config[\"id\"], level, data_file)\n time.sleep(3) # Rate limiting\n elif len(sys.argv) >= 2 and sys.argv[1] == \"--check\":\n # Check insufficient criteria for all projects\n for name, config in PROJECTS.items():\n for level in config[\"levels\"]:\n blockers = check_insufficient_criteria(opener, config[\"id\"], level)\n if blockers:\n print(f\"{name} ({config['id']}) {level}: {len(blockers)} blockers\")\n for n, a in blockers:\n print(f\" {n}: {a}\")\n else:\n print(f\"{name} ({config['id']}) {level}: OK\")\n elif len(sys.argv) >= 2:\n project_id = int(sys.argv[1])\n level = sys.argv[2] if len(sys.argv) > 2 else \"passing\"\n data_file = sys.argv[3] if len(sys.argv) > 3 else None\n\n if data_file:\n submit_level(opener, project_id, level, data_file)\n else:\n for name, config in PROJECTS.items():\n if config[\"id\"] == project_id:\n if level in config[\"levels\"]:\n submit_level(opener, project_id, level, config[\"levels\"][level])\n break\n else:\n print(\"Usage:\")\n print(\" python3 submit-badges.py PROJECT_ID [LEVEL] [DATA_FILE]\")\n print(\" python3 submit-badges.py --all\")\n print(\" python3 submit-badges.py --check (verify criteria sufficiency)\")\n sys.exit(1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":10707,"content_sha256":"b5cba6428ddb7e4f3d4f4f127df613c6f06e8d65c770e8c19c11407757ead5a4"},{"filename":"scripts/verify-badge-criteria.sh","content":"#!/bin/bash\n# verify-badge-criteria.sh - Automated verification of OpenSSF Badge criteria\n# Usage: ./verify-badge-criteria.sh [--level passing|silver|gold]\nset -euo pipefail\n\n# Validate level argument\ncase \"${1:-passing}\" in\n passing|silver|gold) LEVEL=\"${1:-passing}\" ;;\n --help|-h)\n echo \"Usage: $0 [--level passing|silver|gold]\"\n echo \" passing - Check basic OpenSSF criteria (default)\"\n echo \" silver - Check Silver level criteria\"\n echo \" gold - Check Gold level criteria\"\n exit 0\n ;;\n *) echo \"Error: Level must be passing, silver, or gold\"; exit 1 ;;\nesac\n\nSCORE=0\nMAX_SCORE=0\n\necho \"=== OpenSSF Best Practices Badge Verification ===\"\necho \"Level: $LEVEL\"\necho \"\"\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[0;33m'\nNC='\\033[0m' # No Color\n\npass() { echo -e \"${GREEN}✓${NC} $1\"; SCORE=$((SCORE+1)); MAX_SCORE=$((MAX_SCORE+1)); }\nfail() { echo -e \"${RED}✗${NC} $1\"; MAX_SCORE=$((MAX_SCORE+1)); }\nskip() { echo -e \"${YELLOW}○${NC} $1 (N/A)\"; }\n\necho \"=== Basics ===\"\n\n# Check for required files\n[[ -f \"README.md\" ]] && pass \"README.md exists\" || fail \"README.md missing\"\n[[ -f \"LICENSE\" || -f \"LICENSE.md\" || -f \"COPYING\" ]] && pass \"LICENSE file exists\" || fail \"LICENSE file missing\"\n[[ -f \"CONTRIBUTING.md\" ]] && pass \"CONTRIBUTING.md exists\" || fail \"CONTRIBUTING.md missing\"\n[[ -f \"SECURITY.md\" ]] && pass \"SECURITY.md exists\" || fail \"SECURITY.md missing\"\n\nif [[ \"$LEVEL\" != \"passing\" ]]; then\n echo \"\"\n echo \"=== Silver Level Checks ===\"\n\n [[ -f \"GOVERNANCE.md\" ]] && pass \"GOVERNANCE.md exists\" || fail \"GOVERNANCE.md missing\"\n [[ -f \"CODE_OF_CONDUCT.md\" ]] && pass \"CODE_OF_CONDUCT.md exists\" || fail \"CODE_OF_CONDUCT.md missing\"\n [[ -f \"ARCHITECTURE.md\" ]] && pass \"ARCHITECTURE.md exists\" || fail \"ARCHITECTURE.md missing\"\n\n # Check for DCO in CONTRIBUTING.md\n if grep -qi \"Developer Certificate of Origin\\|DCO\\|sign-off\" CONTRIBUTING.md 2>/dev/null; then\n pass \"DCO mentioned in CONTRIBUTING.md\"\n else\n fail \"DCO not mentioned in CONTRIBUTING.md\"\n fi\n\n # Check for 80% coverage threshold in CI\n if grep -rq \"coverage.*80\\|80.*coverage\" .github/workflows/ 2>/dev/null; then\n pass \"80% coverage threshold in CI\"\n else\n fail \"80% coverage threshold not found in CI\"\n fi\nfi\n\nif [[ \"$LEVEL\" == \"gold\" ]]; then\n echo \"\"\n echo \"=== Gold Level Checks ===\"\n\n # Check for SPDX headers\n SPDX_COUNT=$(grep -rl \"SPDX-License-Identifier\" --include=\"*.go\" --include=\"*.py\" --include=\"*.js\" . 2>/dev/null | wc -l)\n SOURCE_COUNT=$(find . -name \"*.go\" -o -name \"*.py\" -o -name \"*.js\" 2>/dev/null | wc -l)\n if [[ \"$SOURCE_COUNT\" -gt 0 && \"$SPDX_COUNT\" -eq \"$SOURCE_COUNT\" ]]; then\n pass \"SPDX headers in all source files ($SPDX_COUNT/$SOURCE_COUNT)\"\n else\n fail \"SPDX headers missing in some files ($SPDX_COUNT/$SOURCE_COUNT)\"\n fi\n\n # Check for 90% coverage threshold\n if grep -rq \"coverage.*90\\|90.*coverage\" .github/workflows/ 2>/dev/null; then\n pass \"90% coverage threshold in CI\"\n else\n fail \"90% coverage threshold not found in CI\"\n fi\n\n # Check for signed tags\n LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"\")\n if [[ -n \"$LATEST_TAG\" ]]; then\n if git tag -v \"$LATEST_TAG\" 2>/dev/null | grep -q \"Good signature\"; then\n pass \"Latest tag ($LATEST_TAG) is signed\"\n else\n fail \"Latest tag ($LATEST_TAG) is not signed\"\n fi\n else\n skip \"No tags found\"\n fi\nfi\n\necho \"\"\necho \"=== Supply Chain Security ===\"\n\n# Check for SLSA provenance\nif grep -rq \"slsa-framework\\|slsa-github-generator\" .github/workflows/ 2>/dev/null; then\n pass \"SLSA provenance generation configured\"\nelse\n fail \"SLSA provenance not configured\"\nfi\n\n# Check for Cosign signing\nif grep -rq \"cosign\\|sigstore\" .github/workflows/ 2>/dev/null; then\n pass \"Artifact signing (Cosign) configured\"\nelse\n fail \"Artifact signing not configured\"\nfi\n\n# Check for SBOM generation\nif grep -rq \"syft\\|sbom\\|cyclonedx\\|spdx\" .github/workflows/ 2>/dev/null; then\n pass \"SBOM generation configured\"\nelse\n fail \"SBOM generation not configured\"\nfi\n\necho \"\"\necho \"=== Quality Gates ===\"\n\n# Check for linting\nif grep -rq \"golangci-lint\\|eslint\\|pylint\\|flake8\" .github/workflows/ 2>/dev/null; then\n pass \"Linting configured in CI\"\nelse\n fail \"Linting not configured\"\nfi\n\n# Check for security scanning\nif grep -rq \"codeql\\|gosec\\|snyk\\|trivy\" .github/workflows/ 2>/dev/null; then\n pass \"Security scanning configured\"\nelse\n fail \"Security scanning not configured\"\nfi\n\n# Check for secret scanning\nif grep -rq \"gitleaks\\|trufflehog\\|detect-secrets\" .github/workflows/ 2>/dev/null; then\n pass \"Secret scanning configured\"\nelse\n fail \"Secret scanning not configured\"\nfi\n\necho \"\"\necho \"=== Summary ===\"\n# Guard against division by zero\nif [[ \"$MAX_SCORE\" -eq 0 ]]; then\n PERCENTAGE=0\nelse\n PERCENTAGE=$((SCORE * 100 / MAX_SCORE))\nfi\necho \"Score: $SCORE/$MAX_SCORE ($PERCENTAGE%)\"\n\nif [[ $PERCENTAGE -ge 90 ]]; then\n echo -e \"${GREEN}Status: Excellent - Ready for $LEVEL certification${NC}\"\nelif [[ $PERCENTAGE -ge 70 ]]; then\n echo -e \"${YELLOW}Status: Good - Minor improvements needed${NC}\"\nelse\n echo -e \"${RED}Status: Needs improvement${NC}\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5342,"content_sha256":"9d47b8d6e01f3bb5b08cc2c0f882f067afb5e4ecd66da9797b3d67ab3d4d3ecd"},{"filename":"scripts/verify-reproducible-build.sh","content":"#!/bin/bash\n# verify-reproducible-build.sh - Verify builds are reproducible (bit-for-bit identical)\n# Usage: ./verify-reproducible-build.sh [build-type] [output-binary]\n# OpenSSF Badge Criteria: build_reproducible (Gold)\n#\n# SECURITY: Uses an allowlist of known build commands instead of eval.\n# Supported build types: go (default), docker, make, composer, npm\nset -euo pipefail\n\n# Build type (not arbitrary command) - validated against allowlist\nBUILD_TYPE=\"${1:-go}\"\nOUTPUT=\"${2:-binary}\"\n\n# Validate that OUTPUT doesn't contain dangerous characters\nif [[ \"$OUTPUT\" =~ [^a-zA-Z0-9._/-] ]]; then\n echo \"Error: Output path contains invalid characters\"\n exit 1\nfi\n\n# Execute build command from allowlist - no eval, no arbitrary input\nrun_build() {\n case \"$BUILD_TYPE\" in\n go)\n go build -trimpath -ldflags '-s -w -buildid=' -o \"$OUTPUT\" .\n ;;\n docker)\n docker build -o \"$OUTPUT\" .\n ;;\n make)\n make build\n ;;\n composer)\n composer install --no-dev --optimize-autoloader\n ;;\n npm)\n npm run build\n ;;\n *)\n echo \"Error: Unknown build type '$BUILD_TYPE'\"\n echo \"Supported types: go, docker, make, composer, npm\"\n exit 1\n ;;\n esac\n}\nTEMP_DIR=$(mktemp -d)\n\necho \"=== Reproducible Build Verification ===\"\necho \"Build type: $BUILD_TYPE\"\necho \"Output file: $OUTPUT\"\necho \"Temp directory: $TEMP_DIR\"\necho \"\"\n\ncleanup() {\n rm -rf \"$TEMP_DIR\"\n}\ntrap cleanup EXIT\n\n# Function to compute hash\ncompute_hash() {\n local file=\"$1\"\n if command -v sha256sum >/dev/null 2>&1; then\n sha256sum \"$file\" | awk '{print $1}'\n elif command -v shasum >/dev/null 2>&1; then\n shasum -a 256 \"$file\" | awk '{print $1}'\n else\n echo \"Error: No SHA256 tool found\"\n exit 1\n fi\n}\n\necho \"=== Build 1 ===\"\necho \"Building...\"\nrun_build\n\nif [ ! -f \"$OUTPUT\" ]; then\n echo \"Error: Build output not found: $OUTPUT\"\n exit 1\nfi\n\nHASH1=$(compute_hash \"$OUTPUT\")\nSIZE1=$(wc -c \u003c \"$OUTPUT\" | tr -d ' ')\necho \"SHA256: $HASH1\"\necho \"Size: $SIZE1 bytes\"\n\n# Save first build\ncp \"$OUTPUT\" \"$TEMP_DIR/build1\"\n\n# Clean and rebuild\necho \"\"\necho \"=== Build 2 ===\"\necho \"Cleaning...\"\nrm -f \"$OUTPUT\"\n\n# Add small delay to ensure different timestamp\nsleep 1\n\necho \"Building...\"\nrun_build\n\nif [ ! -f \"$OUTPUT\" ]; then\n echo \"Error: Second build output not found: $OUTPUT\"\n exit 1\nfi\n\nHASH2=$(compute_hash \"$OUTPUT\")\nSIZE2=$(wc -c \u003c \"$OUTPUT\" | tr -d ' ')\necho \"SHA256: $HASH2\"\necho \"Size: $SIZE2 bytes\"\n\n# Save second build\ncp \"$OUTPUT\" \"$TEMP_DIR/build2\"\n\necho \"\"\necho \"=== Comparison ===\"\n\nif [ \"$HASH1\" = \"$HASH2\" ]; then\n echo \"✓ Builds are IDENTICAL\"\n echo \"\"\n echo \"SHA256: $HASH1\"\n echo \"Size: $SIZE1 bytes\"\n echo \"\"\n echo \"The build is reproducible!\"\n echo \"\"\n echo \"OpenSSF Badge: build_reproducible = Met\"\n exit 0\nelse\n echo \"✗ Builds are DIFFERENT\"\n echo \"\"\n echo \"Build 1: $HASH1 ($SIZE1 bytes)\"\n echo \"Build 2: $HASH2 ($SIZE2 bytes)\"\n echo \"\"\n\n # Try to identify differences\n if command -v xxd >/dev/null 2>&1 && command -v diff >/dev/null 2>&1; then\n echo \"=== Binary Differences ===\"\n echo \"First 10 differences:\"\n diff \u003c(xxd \"$TEMP_DIR/build1\") \u003c(xxd \"$TEMP_DIR/build2\") 2>/dev/null | head -20 || true\n fi\n\n echo \"\"\n echo \"=== Troubleshooting ===\"\n echo \"\"\n echo \"Common causes of non-reproducible builds:\"\n echo \"\"\n echo \"1. Embedded timestamps\"\n echo \" Fix: Use SOURCE_DATE_EPOCH environment variable\"\n echo \"\"\n echo \"2. Embedded build paths\"\n echo \" Fix (Go): Use -trimpath flag\"\n echo \" Fix (C): Use -ffile-prefix-map\"\n echo \"\"\n echo \"3. Build ID embedded in binary\"\n echo \" Fix (Go): Use -ldflags '-buildid='\"\n echo \"\"\n echo \"4. Non-deterministic link order\"\n echo \" Fix: Sort inputs, use deterministic linker\"\n echo \"\"\n echo \"5. Parallel build race conditions\"\n echo \" Fix: Use -j1 for make, single-threaded builds\"\n echo \"\"\n echo \"Recommended Go build command:\"\n echo \" CGO_ENABLED=0 go build -trimpath -ldflags '-s -w -buildid=' -o binary .\"\n echo \"\"\n echo \"For more info: https://reproducible-builds.org/\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":4310,"content_sha256":"fe586568dc3301cfecb63b874db54b7334b38e68e6171d27f350d81beee1f7e3"},{"filename":"scripts/verify-review-requirements.sh","content":"#!/bin/bash\n# verify-review-requirements.sh - Verify PR review requirements meet badge level\n# Usage: ./verify-review-requirements.sh [--level silver|gold] [--owner owner] [--repo repo]\n# OpenSSF Badge Criteria: two_person_review (Gold), code_review (Silver)\nset -euo pipefail\n\nLEVEL=\"silver\"\nOWNER=\"\"\nREPO=\"\"\nBRANCH=\"main\"\n\n# Parse arguments\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --level)\n LEVEL=\"$2\"\n shift 2\n ;;\n --owner)\n OWNER=\"$2\"\n shift 2\n ;;\n --repo)\n REPO=\"$2\"\n shift 2\n ;;\n --branch)\n BRANCH=\"$2\"\n shift 2\n ;;\n *)\n shift\n ;;\n esac\ndone\n\necho \"=== PR Review Requirements Verification ===\"\necho \"Badge Level: $LEVEL\"\necho \"\"\n\n# Determine required reviewers based on level\ncase \"$LEVEL\" in\n passing)\n REQUIRED_REVIEWERS=0\n ;;\n silver)\n REQUIRED_REVIEWERS=1\n ;;\n gold)\n REQUIRED_REVIEWERS=2\n ;;\n *)\n echo \"Error: Invalid level. Use passing, silver, or gold.\"\n exit 1\n ;;\nesac\n\necho \"Required reviewers for $LEVEL level: $REQUIRED_REVIEWERS\"\necho \"\"\n\n# Try to detect owner/repo from git remote\nif [ -z \"$OWNER\" ] || [ -z \"$REPO\" ]; then\n REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo \"\")\n if [ -n \"$REMOTE_URL\" ]; then\n # Extract owner/repo from various URL formats\n if [[ \"$REMOTE_URL\" =~ github\\.com[:/]([^/]+)/([^/.]+) ]]; then\n OWNER=\"${BASH_REMATCH[1]}\"\n REPO=\"${BASH_REMATCH[2]}\"\n fi\n fi\nfi\n\nif [ -z \"$OWNER\" ] || [ -z \"$REPO\" ]; then\n echo \"Error: Could not determine repository. Use --owner and --repo flags.\"\n exit 1\nfi\n\necho \"Repository: $OWNER/$REPO\"\necho \"Branch: $BRANCH\"\necho \"\"\n\n# Check if gh CLI is available\nif ! command -v gh >/dev/null 2>&1; then\n echo \"Error: GitHub CLI (gh) is required but not installed.\"\n echo \"Install from: https://cli.github.com/\"\n exit 1\nfi\n\n# Check authentication\nif ! gh auth status >/dev/null 2>&1; then\n echo \"Error: GitHub CLI is not authenticated.\"\n echo \"Run: gh auth login\"\n exit 1\nfi\n\n# Fetch branch protection settings\necho \"Fetching branch protection settings...\"\necho \"\"\n\nPROTECTION=$(gh api \"repos/$OWNER/$REPO/branches/$BRANCH/protection\" 2>/dev/null || echo \"\")\n\nif [ -z \"$PROTECTION\" ]; then\n echo \"✗ No branch protection configured for $BRANCH\"\n echo \"\"\n echo \"To enable branch protection via GitHub CLI:\"\n echo \" gh api repos/$OWNER/$REPO/branches/$BRANCH/protection -X PUT -f required_approving_review_count=$REQUIRED_REVIEWERS\"\n exit 1\nfi\n\n# Extract review requirements\nREVIEW_PROTECTION=$(gh api \"repos/$OWNER/$REPO/branches/$BRANCH/protection/required_pull_request_reviews\" 2>/dev/null || echo \"\")\n\nif [ -z \"$REVIEW_PROTECTION\" ]; then\n ACTUAL_REVIEWERS=0\n DISMISS_STALE=\"false\"\n REQUIRE_CODEOWNERS=\"false\"\nelse\n ACTUAL_REVIEWERS=$(echo \"$REVIEW_PROTECTION\" | jq -r '.required_approving_review_count // 0')\n DISMISS_STALE=$(echo \"$REVIEW_PROTECTION\" | jq -r '.dismiss_stale_reviews // false')\n REQUIRE_CODEOWNERS=$(echo \"$REVIEW_PROTECTION\" | jq -r '.require_code_owner_reviews // false')\nfi\n\necho \"=== Current Settings ===\"\necho \"Required approving reviews: $ACTUAL_REVIEWERS\"\necho \"Dismiss stale reviews: $DISMISS_STALE\"\necho \"Require code owner reviews: $REQUIRE_CODEOWNERS\"\necho \"\"\n\n# Check required status checks\nSTATUS_CHECKS=$(echo \"$PROTECTION\" | jq -r '.required_status_checks.contexts[]? // empty' 2>/dev/null | wc -l | tr -d ' ')\necho \"Required status checks: $STATUS_CHECKS\"\n\n# Check enforce admins\nENFORCE_ADMINS=$(echo \"$PROTECTION\" | jq -r '.enforce_admins.enabled // false')\necho \"Enforce for admins: $ENFORCE_ADMINS\"\n\necho \"\"\necho \"=== Assessment ===\"\necho \"\"\n\nPASSED=true\n\n# Check reviewer count\nif [ \"$ACTUAL_REVIEWERS\" -ge \"$REQUIRED_REVIEWERS\" ]; then\n if [ \"$LEVEL\" = \"gold\" ] && [ \"$ACTUAL_REVIEWERS\" -ge 2 ]; then\n echo \"✓ Two-person review requirement met ($ACTUAL_REVIEWERS reviewers)\"\n elif [ \"$LEVEL\" = \"silver\" ] && [ \"$ACTUAL_REVIEWERS\" -ge 1 ]; then\n echo \"✓ Code review requirement met ($ACTUAL_REVIEWERS reviewer(s))\"\n else\n echo \"✓ Review requirement met ($ACTUAL_REVIEWERS reviewer(s))\"\n fi\nelse\n echo \"✗ Insufficient reviewers: $ACTUAL_REVIEWERS \u003c $REQUIRED_REVIEWERS required\"\n PASSED=false\nfi\n\n# Additional checks for Silver/Gold\nif [ \"$LEVEL\" = \"silver\" ] || [ \"$LEVEL\" = \"gold\" ]; then\n if [ \"$DISMISS_STALE\" = \"true\" ]; then\n echo \"✓ Stale reviews dismissed on new commits\"\n else\n echo \"⚠ Recommend: Enable 'Dismiss stale reviews'\"\n fi\n\n if [ \"$STATUS_CHECKS\" -gt 0 ]; then\n echo \"✓ Status checks required ($STATUS_CHECKS checks)\"\n else\n echo \"⚠ Recommend: Add required status checks\"\n fi\nfi\n\n# Additional checks for Gold\nif [ \"$LEVEL\" = \"gold\" ]; then\n if [ \"$REQUIRE_CODEOWNERS\" = \"true\" ]; then\n echo \"✓ Code owner reviews required\"\n else\n echo \"⚠ Recommend: Enable code owner reviews\"\n fi\n\n if [ \"$ENFORCE_ADMINS\" = \"true\" ]; then\n echo \"✓ Rules enforced for administrators\"\n else\n echo \"⚠ Recommend: Enable 'Do not allow bypassing'\"\n fi\nfi\n\necho \"\"\nif [ \"$PASSED\" = true ]; then\n echo \"OpenSSF Badge: Review requirements for $LEVEL level = Met\"\n exit 0\nelse\n echo \"OpenSSF Badge: Review requirements for $LEVEL level = Unmet\"\n echo \"\"\n echo \"To update via GitHub CLI:\"\n echo \" gh api repos/$OWNER/$REPO/branches/$BRANCH/protection/required_pull_request_reviews \\\\\"\n echo \" -X PATCH -f required_approving_review_count=$REQUIRED_REVIEWERS\"\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5746,"content_sha256":"a6e44d1f3d2a191e659700fc638ea3f679ec9341b9fdfca0f71a0edaa47e08b8"},{"filename":"scripts/verify-signed-tags.sh","content":"#!/bin/bash\n# verify-signed-tags.sh - Verify git tags are cryptographically signed\n# Usage: ./verify-signed-tags.sh [tag] [--check-all]\n# OpenSSF Badge Criteria: version_tags_signed (Silver)\nset -euo pipefail\n\nTAG=\"${1:-}\"\nCHECK_ALL=false\n\n# Parse arguments\nfor arg in \"$@\"; do\n case \"$arg\" in\n --check-all)\n CHECK_ALL=true\n ;;\n esac\ndone\n\necho \"=== Git Tag Signature Verification ===\"\necho \"\"\n\n# Check for GPG\nif ! command -v gpg >/dev/null 2>&1; then\n echo \"Warning: GPG is not installed. Tag verification may be limited.\"\nfi\n\n# Function to verify a single tag\nverify_tag() {\n local tag=\"$1\"\n local result\n\n # Check if tag exists\n if ! git rev-parse \"$tag\" >/dev/null 2>&1; then\n echo \"✗ Tag '$tag' does not exist\"\n return 1\n fi\n\n # Check if it's an annotated tag\n local tag_type\n tag_type=$(git cat-file -t \"$tag\" 2>/dev/null || echo \"unknown\")\n\n if [ \"$tag_type\" != \"tag\" ]; then\n echo \"✗ $tag: Lightweight tag (not annotated, cannot be signed)\"\n return 1\n fi\n\n # Try to verify signature\n if git tag -v \"$tag\" >/dev/null 2>&1; then\n echo \"✓ $tag: Signed and verified\"\n return 0\n else\n # Check if tag has signature but verification failed\n if git cat-file tag \"$tag\" 2>/dev/null | grep -q \"BEGIN PGP SIGNATURE\"; then\n echo \"⚠ $tag: Has signature but verification failed (missing public key?)\"\n return 1\n else\n echo \"✗ $tag: Annotated but NOT signed\"\n return 1\n fi\n fi\n}\n\nif [ -n \"$TAG\" ] && [ \"$TAG\" != \"--check-all\" ]; then\n # Verify specific tag\n echo \"Verifying tag: $TAG\"\n echo \"\"\n verify_tag \"$TAG\"\n exit $?\nfi\n\nif [ \"$CHECK_ALL\" = true ]; then\n # Verify all tags\n echo \"Checking all tags...\"\n echo \"\"\n\n TOTAL=0\n SIGNED=0\n UNSIGNED=0\n\n for tag in $(git tag -l); do\n TOTAL=$((TOTAL + 1))\n if verify_tag \"$tag\"; then\n SIGNED=$((SIGNED + 1))\n else\n UNSIGNED=$((UNSIGNED + 1))\n fi\n done\n\n echo \"\"\n echo \"=== Summary ===\"\n echo \"Total tags: $TOTAL\"\n echo \"Signed: $SIGNED\"\n echo \"Unsigned/Invalid: $UNSIGNED\"\n\n if [ \"$TOTAL\" -eq 0 ]; then\n echo \"\"\n echo \"No tags found in repository.\"\n exit 0\n fi\n\n # Calculate percentage using awk (POSIX-compatible)\n PCT=$(awk -v s=\"$SIGNED\" -v t=\"$TOTAL\" 'BEGIN { printf \"%.1f\", (s/t)*100 }')\n echo \"Signing rate: $PCT%\"\n\n if [ \"$UNSIGNED\" -gt 0 ]; then\n echo \"\"\n echo \"OpenSSF Badge: version_tags_signed = Unmet\"\n echo \"\"\n echo \"To sign future tags:\"\n echo \" git tag -s v1.0.0 -m 'Release v1.0.0'\"\n echo \"\"\n echo \"To sign existing tags (creates new signed tag):\"\n echo \" git tag -s -f v1.0.0 v1.0.0^{} -m 'Release v1.0.0'\"\n exit 1\n else\n echo \"\"\n echo \"OpenSSF Badge: version_tags_signed = Met\"\n exit 0\n fi\nelse\n # Check latest release tags\n echo \"Checking recent release tags...\"\n echo \"\"\n\n RELEASE_TAGS=$(git tag -l 'v*' --sort=-version:refname 2>/dev/null | head -5)\n\n if [ -z \"$RELEASE_TAGS\" ]; then\n echo \"No release tags (v*) found.\"\n echo \"\"\n echo \"To create a signed release tag:\"\n echo \" git tag -s v1.0.0 -m 'Release v1.0.0'\"\n exit 0\n fi\n\n UNSIGNED_COUNT=0\n for tag in $RELEASE_TAGS; do\n if ! verify_tag \"$tag\"; then\n UNSIGNED_COUNT=$((UNSIGNED_COUNT + 1))\n fi\n done\n\n echo \"\"\n if [ \"$UNSIGNED_COUNT\" -gt 0 ]; then\n echo \"OpenSSF Badge: version_tags_signed = Unmet\"\n exit 1\n else\n echo \"OpenSSF Badge: version_tags_signed = Met\"\n exit 0\n fi\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3761,"content_sha256":"a3b93cc781c622de710bddbbebde7538fdf65c8e88361397ba3a9be50a9d8acf"},{"filename":"scripts/verify-spdx-headers.sh","content":"#!/bin/bash\n# verify-spdx-headers.sh - Verify SPDX license headers exist in source files\n# Usage: ./verify-spdx-headers.sh [--fix] [directory]\n# OpenSSF Badge Criteria: license_per_file, copyright_per_file (Gold)\nset -euo pipefail\n\nFIX_MODE=false\nTARGET_DIR=\".\"\n\n# Parse arguments\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --fix)\n FIX_MODE=true\n shift\n ;;\n *)\n TARGET_DIR=\"$1\"\n shift\n ;;\n esac\ndone\n\necho \"=== SPDX Header Verification ===\"\necho \"Directory: $TARGET_DIR\"\necho \"Mode: $([ \"$FIX_MODE\" = true ] && echo 'Fix' || echo 'Check only')\"\necho \"\"\n\n# Supported file extensions (POSIX-compatible, no bash 4+ associative arrays)\nSUPPORTED_EXTS=\"go py js ts jsx tsx rs java c cpp h sh rb\"\n\n# Exclude patterns\nEXCLUDE_DIRS=\"vendor|node_modules|.git|dist|build|.venv|__pycache__\"\n\nTOTAL=0\nMISSING=0\nMISSING_FILES=\"\"\n\necho \"=== Checking Files ===\"\necho \"\"\n\nfor ext in $SUPPORTED_EXTS; do\n # Find files with this extension, excluding common non-source directories\n while IFS= read -r -d '' file; do\n TOTAL=$((TOTAL + 1))\n\n # Check for SPDX header in first 10 lines\n if ! head -10 \"$file\" | grep -q \"SPDX-License-Identifier\"; then\n MISSING=$((MISSING + 1))\n MISSING_FILES=\"${MISSING_FILES}${file}\n\"\n echo \"✗ Missing SPDX header: $file\"\n fi\n done \u003c \u003c(find \"$TARGET_DIR\" -type f -name \"*.$ext\" \\\n ! -path \"*/$EXCLUDE_DIRS/*\" \\\n -print0 2>/dev/null)\ndone\n\necho \"\"\necho \"=== Summary ===\"\necho \"Total files checked: $TOTAL\"\necho \"Files with SPDX headers: $((TOTAL - MISSING))\"\necho \"Files missing headers: $MISSING\"\n\nif [ \"$TOTAL\" -eq 0 ]; then\n echo \"\"\n echo \"No source files found to check.\"\n exit 0\nfi\n\n# Calculate percentage using awk (POSIX-compatible)\nPCT=$(awk -v m=\"$MISSING\" -v t=\"$TOTAL\" 'BEGIN { printf \"%.1f\", ((t-m)/t)*100 }')\necho \"Coverage: $PCT%\"\necho \"\"\n\nif [ \"$MISSING\" -eq 0 ]; then\n echo \"✓ All source files have SPDX license headers\"\n exit 0\nelse\n echo \"✗ $MISSING file(s) missing SPDX headers\"\n echo \"\"\n\n if [ \"$FIX_MODE\" = true ]; then\n # Check if add-spdx-headers.sh exists in the same directory\n SCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n if [ -x \"$SCRIPT_DIR/add-spdx-headers.sh\" ]; then\n echo \"Running add-spdx-headers.sh to fix missing headers...\"\n \"$SCRIPT_DIR/add-spdx-headers.sh\"\n exit $?\n else\n echo \"Error: add-spdx-headers.sh not found or not executable\"\n echo \"Please run: ./add-spdx-headers.sh [license] [copyright]\"\n exit 1\n fi\n else\n echo \"Files missing headers:\"\n printf \"%s\" \"$MISSING_FILES\" | head -20\n if [ \"$MISSING\" -gt 20 ]; then\n echo \"... and $((MISSING - 20)) more\"\n fi\n echo \"\"\n echo \"To add headers automatically, run:\"\n echo \" ./add-spdx-headers.sh [license] [copyright]\"\n echo \"\"\n echo \"Or run this script with --fix flag:\"\n echo \" ./verify-spdx-headers.sh --fix\"\n fi\n exit 1\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3115,"content_sha256":"b2ef711696b16a1906fc947646eb952c4a367b117a5352de5d95f5a139641125"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Enterprise Readiness Assessment","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Production/enterprise tier only — see ","type":"text"},{"text":"references/tier-framing.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Production/enterprise readiness evaluations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Supply chain security: SLSA provenance, cosign signing, SBOMs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CI/CD hardening, workflow permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OpenSSF Best Practices (Passing/Silver/Gold), OSPS Baseline (L1/2/3)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scorecard optimization (Token-Permissions, Branch-Protection, Pinned-Deps)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Code review, ADRs, changelogs, SECURITY.md","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Assessment Workflow","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Discovery","type":"text","marks":[{"type":"strong"}]},{"text":": Identify platform, languages, existing CI/CD, dependabot.yml","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scoring","type":"text","marks":[{"type":"strong"}]},{"text":": Apply checklists; check Scorecard, badge criteria, coverage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Gap Analysis","type":"text","marks":[{"type":"strong"}]},{"text":": List missing controls by severity","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implementation","type":"text","marks":[{"type":"strong"}]},{"text":": Apply fixes (SHA-pin actions, harden permissions, add workflows)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verification","type":"text","marks":[{"type":"strong"}]},{"text":": Re-score and compare","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Mandatory Workflows & Badges","type":"text"}]},{"type":"paragraph","content":[{"text":"Required coverage: CI, CodeQL, Scorecard, dependency review, composer audit, SBOM — as dedicated workflows or jobs calling the netresearch reusable. Badges: CI, Codecov, Scorecard, Best Practices, Baseline. See ","type":"text"},{"text":"references/badges-and-workflows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Key Hardening Patterns","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Permissions","type":"text","marks":[{"type":"strong"}]},{"text":": Declare ","type":"text"},{"text":"permissions: contents: read","type":"text","marks":[{"type":"code_inline"}]},{"text":" at workflow-level; grant write only per-job","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SHA pinning","type":"text","marks":[{"type":"strong"}]},{"text":": Third-party actions pinned to SHA with version comment (","type":"text"},{"text":"# v4.2.0","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Org-internal reusable workflows use ","type":"text"},{"text":"@main","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Harden-Runner","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"step-security/harden-runner","type":"text","marks":[{"type":"code_inline"}]},{"text":" as first step in every job; prefer ","type":"text"},{"text":"egress-policy: block","type":"text","marks":[{"type":"code_inline"}]},{"text":" with allowed-endpoints","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dependabot","type":"text","marks":[{"type":"strong"}]},{"text":": Configure ","type":"text"},{"text":"dependabot.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" with all ecosystems (","type":"text"},{"text":"composer","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"npm","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"github-actions","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"docker","type":"text","marks":[{"type":"code_inline"}]},{"text":"); set up auto-merge workflow for dependency PRs using ","type":"text"},{"text":"pull_request_target","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Coverage","type":"text","marks":[{"type":"strong"}]},{"text":": Upload via ","type":"text"},{"text":"codecov-action","type":"text","marks":[{"type":"code_inline"}]},{"text":"; configure ","type":"text"},{"text":"codecov.yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" with patch coverage threshold","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Duplicate CI prevention","type":"text","marks":[{"type":"strong"}]},{"text":": Scope ","type":"text"},{"text":"push:","type":"text","marks":[{"type":"code_inline"}]},{"text":" trigger to ","type":"text"},{"text":"branches: [main]","type":"text","marks":[{"type":"code_inline"}]},{"text":" when ","type":"text"},{"text":"pull_request:","type":"text","marks":[{"type":"code_inline"}]},{"text":" is also present","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SLSA provenance","type":"text","marks":[{"type":"strong"}]},{"text":": Use ","type":"text"},{"text":"actions/attest-build-provenance","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"id-token: write","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"attestations: write","type":"text","marks":[{"type":"code_inline"}]},{"text":" permissions; verify with ","type":"text"},{"text":"gh attestation verify","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security policy","type":"text","marks":[{"type":"strong"}]},{"text":": Create ","type":"text"},{"text":"SECURITY.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" with vulnerability disclosure process and response SLA (Critical: 7 days, High: 30 days)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Critical Rules","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER","type":"text","marks":[{"type":"strong"}]},{"text":" interpolate ","type":"text"},{"text":"${{ github.event.* }}","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"${{ inputs.* }}","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"run:","type":"text","marks":[{"type":"code_inline"}]},{"text":" blocks (script injection)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER","type":"text","marks":[{"type":"strong"}]},{"text":" guess action versions -- fetch from GitHub API and verify SHA against tags","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ALWAYS","type":"text","marks":[{"type":"strong"}]},{"text":" include ","type":"text"},{"text":"https://","type":"text","marks":[{"type":"code_inline"}]},{"text":" URLs in badge justifications","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ALWAYS","type":"text","marks":[{"type":"strong"}]},{"text":" configure auto-merge for repos with Dependabot/Renovate","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reference","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/general.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Always","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/scorecard-playbook.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scorecard optimization","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/badges-and-workflows.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Badge URLs, workflows","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/mandatory-requirements.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Checklist","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/ci-patterns.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CI/CD, hooks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/code-review.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PR quality","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/documentation.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ADRs, changelogs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/slsa-provenance.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SLSA Level 3","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/signed-releases.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cosign/GPG","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/openssf-badge-silver.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Silver","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/openssf-badge-gold.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Gold","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/openssf-badge-baseline.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OSPS Baseline","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/harden-runner-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Harden-Runner","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/solo-maintainer-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"N/A criteria","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/npm-pnpm-supply-chain.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pnpm","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Related skills: ","type":"text"},{"text":"go-development","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"github-project","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"security-audit","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"git-workflow","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Credits & Attribution","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill is based on the excellent work by ","type":"text"},{"text":"Netresearch DTT GmbH","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.netresearch.de/","title":null}},{"type":"strong"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"Original repository: https://github.com/netresearch/enterprise-readiness-skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Copyright (c) Netresearch DTT GmbH","type":"text","marks":[{"type":"strong"}]},{"text":" — Methodology and best practices (MIT / CC-BY-SA-4.0)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Special thanks to ","type":"text"},{"text":"Netresearch DTT GmbH","type":"text","marks":[{"type":"link","attrs":{"href":"https://www.netresearch.de/","title":null}}]},{"text":" for their generous open-source contributions to the TYPO3 community, which helped shape this skill collection. Adapted by webconsulting.at for this skill collection","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"enterprise-readiness","author":"@skillopedia","source":{"stars":29,"repo_name":"webconsulting-skills","origin_url":"https://github.com/dirnbauer/webconsulting-skills/blob/HEAD/skills/enterprise-readiness/SKILL.md","repo_owner":"dirnbauer","body_sha256":"64df8c82c4fa3692d0b8b63573d05d335e92cf2da55e1208394cb608856d3e5c","cluster_key":"29b7523db763009e54f903ee2dfceb7c02d3a9325c652bb323a21077a81eee2e","clean_bundle":{"format":"clean-skill-bundle-v1","source":"dirnbauer/webconsulting-skills/skills/enterprise-readiness/SKILL.md","attachments":[{"id":"441571ae-56e4-5434-960e-b322104385e2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/441571ae-56e4-5434-960e-b322104385e2/attachment.yaml","path":"checkpoints.yaml","size":41715,"sha256":"9ad2ddfe679ca400029c1ca7c0b25a2d86a5eb5ba27b725ddbf069fe0c009810","contentType":"application/yaml; charset=utf-8"},{"id":"32488287-707c-5875-b745-c3b843e60c6b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/32488287-707c-5875-b745-c3b843e60c6b/attachment.json","path":"evals/evals.json","size":9116,"sha256":"a179533cbb2af3fd4e16e76a78c9d6b8db57f8b4c55c02b192b987ca6e22cba5","contentType":"application/json; charset=utf-8"},{"id":"57e32ad6-6946-5e95-8f1a-c2ef824dc2c3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/57e32ad6-6946-5e95-8f1a-c2ef824dc2c3/attachment.md","path":"references/2fa-enforcement.md","size":6528,"sha256":"c40fe8dbf7a5f9d4282eebd76570f29a799f3c5103a29bb8d5205818b9ae84d0","contentType":"text/markdown; charset=utf-8"},{"id":"ea17e11b-b293-560b-b728-2c17d5b262d1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ea17e11b-b293-560b-b728-2c17d5b262d1/attachment.md","path":"references/badge-display.md","size":7636,"sha256":"a6e27ff250b4d8f4a4e6ba64ee56123e3693a6036fe12b3c4c6be9d89427963a","contentType":"text/markdown; charset=utf-8"},{"id":"14b543d9-638d-56db-94b6-67931fcd090e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/14b543d9-638d-56db-94b6-67931fcd090e/attachment.md","path":"references/badge-submission-api.md","size":14160,"sha256":"baf7ea1c9fff943774c2cfefef50c5ee9bf513a90c1baf6d3ec38aafdac77d09","contentType":"text/markdown; charset=utf-8"},{"id":"ed4e9ab1-a4c2-5cb4-a92b-154da9211c43","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ed4e9ab1-a4c2-5cb4-a92b-154da9211c43/attachment.md","path":"references/badges-and-workflows.md","size":1207,"sha256":"421c4628ddf2c82e1291e486938cda722095acedd6598fed5ec07c6f84030033","contentType":"text/markdown; charset=utf-8"},{"id":"5ed8d7bc-e062-5fef-8575-d9e3f2c7d6ae","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ed8d7bc-e062-5fef-8575-d9e3f2c7d6ae/attachment.md","path":"references/branch-coverage.md","size":9443,"sha256":"d5b057b3244977d71f99282f3704dc578fc8eaa672631d7d5eb8dd94a6c59134","contentType":"text/markdown; charset=utf-8"},{"id":"5943c9d7-2d64-5857-b9c3-785aef9ef676","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5943c9d7-2d64-5857-b9c3-785aef9ef676/attachment.md","path":"references/ci-docker-worktree.md","size":4810,"sha256":"f9edf9286d6c2d74c92ca41895eedea5d973aa149ce5ba54ab3a5de96e2f2cb6","contentType":"text/markdown; charset=utf-8"},{"id":"19b6b440-39a8-5eb5-be4a-9c5e854a3a0d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/19b6b440-39a8-5eb5-be4a-9c5e854a3a0d/attachment.md","path":"references/ci-patterns.md","size":17889,"sha256":"7156e37610fc01681facbc3c1fa7b0e8dd6325f06640fbf3b9dd300ace0cbe5f","contentType":"text/markdown; charset=utf-8"},{"id":"9f542333-80fe-500b-bf76-33dcc5c03bd7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9f542333-80fe-500b-bf76-33dcc5c03bd7/attachment.md","path":"references/code-review.md","size":7664,"sha256":"f84b7eb7ed798fd064246c8bb37892aafe1ee19b3b6ec925be70f8e557cc1e3f","contentType":"text/markdown; charset=utf-8"},{"id":"6d23c9d6-9338-5df1-9090-b36be26ef52b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6d23c9d6-9338-5df1-9090-b36be26ef52b/attachment.md","path":"references/cve-workflow.md","size":1276,"sha256":"2d64c8da1821914f30ec8df645c664148cda4e90ea6a014948a7f1e6c48e15de","contentType":"text/markdown; charset=utf-8"},{"id":"36366e8e-d514-5284-90ef-71517400d871","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/36366e8e-d514-5284-90ef-71517400d871/attachment.md","path":"references/dco-implementation.md","size":4296,"sha256":"52cd7d3e4c5ce43481746e2484962933997a5ee44f6f0c258727ca767a25d8ec","contentType":"text/markdown; charset=utf-8"},{"id":"382babfe-513a-5859-8698-1e1c17c09f63","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/382babfe-513a-5859-8698-1e1c17c09f63/attachment.md","path":"references/documentation.md","size":7832,"sha256":"32bc63660058604d1947bf4890b8b891bb02abfb27abf9e92e637b9ad430e4ba","contentType":"text/markdown; charset=utf-8"},{"id":"e743edeb-1330-57de-96f5-34f91e1c9627","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e743edeb-1330-57de-96f5-34f91e1c9627/attachment.md","path":"references/dynamic-analysis.md","size":10248,"sha256":"512d9a7f4e7fdee030560a73471b5401214fc5d9c210a81666c9b2c610851c58","contentType":"text/markdown; charset=utf-8"},{"id":"44858f04-c7de-57aa-a55c-857892fb4e8d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/44858f04-c7de-57aa-a55c-857892fb4e8d/attachment.md","path":"references/general.md","size":9718,"sha256":"0377f4ba875626501e21962ebb1850a838896d3d74cdcb8eff00c136c22cc95f","contentType":"text/markdown; charset=utf-8"},{"id":"483d4044-302c-5c90-a4be-3a7be167df17","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/483d4044-302c-5c90-a4be-3a7be167df17/attachment.md","path":"references/github.md","size":19943,"sha256":"30fd18bd27fbf6a32d040a7872a7bff9dd9b147866c40f6d6bd71f67148c1c83","contentType":"text/markdown; charset=utf-8"},{"id":"a20a755b-b0df-5f57-a294-6ec48d53b0c5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a20a755b-b0df-5f57-a294-6ec48d53b0c5/attachment.md","path":"references/go.md","size":7296,"sha256":"039041ff7c0a72556a592112666c9c525111cf4cedf023edd3915b51eff6d69a","contentType":"text/markdown; charset=utf-8"},{"id":"e8e3fb41-55db-55b2-9950-18ff30e577ab","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e8e3fb41-55db-55b2-9950-18ff30e577ab/attachment.md","path":"references/harden-runner-guide.md","size":3680,"sha256":"da06d54f6e3c0633023ad1a398ae4d301ddb6ee4b862c5637e63f2b68a5abe65","contentType":"text/markdown; charset=utf-8"},{"id":"c06e5ccc-d92d-59f6-99bb-3f4a1ed955db","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c06e5ccc-d92d-59f6-99bb-3f4a1ed955db/attachment.md","path":"references/mandatory-requirements.md","size":7418,"sha256":"3add2004ce60ff8446a9838ebe849648efad3c6c233d4d8620b5c7a326e7113f","contentType":"text/markdown; charset=utf-8"},{"id":"80b27833-8984-5845-a43d-910c6742bd3a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/80b27833-8984-5845-a43d-910c6742bd3a/attachment.md","path":"references/npm-pnpm-supply-chain.md","size":7766,"sha256":"9bca5d184c37934b6b8398d4e17b2541eb2e9e8d2016cabdf846e4cbaf5578b5","contentType":"text/markdown; charset=utf-8"},{"id":"554ed853-a8cc-5df1-8f72-ceb0c64fcf03","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/554ed853-a8cc-5df1-8f72-ceb0c64fcf03/attachment.md","path":"references/openssf-badge-baseline.md","size":6505,"sha256":"d93ee0968b2c0097a687d487b39a953ce578426df447b089e36d667a28a42fc9","contentType":"text/markdown; charset=utf-8"},{"id":"18a45ce8-8ab7-51b7-be8f-5ceed1122fac","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/18a45ce8-8ab7-51b7-be8f-5ceed1122fac/attachment.md","path":"references/openssf-badge-gold.md","size":11494,"sha256":"247b70eb32e19b763c9f303d6a15891147e84505e330f1c826b39ab36135f01f","contentType":"text/markdown; charset=utf-8"},{"id":"cea08409-a1ae-5fbf-a346-b28ee91af08a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cea08409-a1ae-5fbf-a346-b28ee91af08a/attachment.md","path":"references/openssf-badge-silver.md","size":14168,"sha256":"f6841c95b34ae430e5d30a47f38843b8ba146b8b641f6e45b12cb360baf86029","contentType":"text/markdown; charset=utf-8"},{"id":"f7eb57a0-a96c-5c6f-be50-f194cba5c340","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f7eb57a0-a96c-5c6f-be50-f194cba5c340/attachment.md","path":"references/quick-start-guide.md","size":5173,"sha256":"7d0b3631c8d634af26ef6ae918f6c7a1e5a6c44b0afdc088fad6623f6986869d","contentType":"text/markdown; charset=utf-8"},{"id":"bcc45ba0-9905-5a0c-9aaf-6e77b80f2454","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bcc45ba0-9905-5a0c-9aaf-6e77b80f2454/attachment.md","path":"references/reproducible-builds.md","size":5572,"sha256":"c9d3100107c29a3a55d3ea255a66c7d037d8a13d32c6cadb38e803f50d710a4f","contentType":"text/markdown; charset=utf-8"},{"id":"fa5ba6f6-bddf-5553-8a4b-bb8de0123d44","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fa5ba6f6-bddf-5553-8a4b-bb8de0123d44/attachment.md","path":"references/scorecard-playbook.md","size":7010,"sha256":"27c98bab42fa01b223824d3963416406487242890da8c2dcacfe7763f93b32c3","contentType":"text/markdown; charset=utf-8"},{"id":"41f74dfd-0069-5a4a-878a-7094ef371a87","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/41f74dfd-0069-5a4a-878a-7094ef371a87/attachment.md","path":"references/security-hardening.md","size":12572,"sha256":"febddd97629f67b0c631912c1448cddcf709035cd912357367543d79f39da1ae","contentType":"text/markdown; charset=utf-8"},{"id":"d59bcf90-22f6-52cc-9a4f-b6eb5895a1c3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d59bcf90-22f6-52cc-9a4f-b6eb5895a1c3/attachment.md","path":"references/signed-releases.md","size":5839,"sha256":"d717c88095af0d36cbcb6f0f3a775c8885ed9b33ca804ef154ec333eb7e1ac0a","contentType":"text/markdown; charset=utf-8"},{"id":"ee10e284-5390-5726-90a3-8b4430563201","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ee10e284-5390-5726-90a3-8b4430563201/attachment.md","path":"references/slsa-provenance.md","size":13472,"sha256":"bb035f26a58ff930dd95c056a439ca0ad3fe02877c794555546030eaf4cbcf97","contentType":"text/markdown; charset=utf-8"},{"id":"fbc303d5-3dc6-54dd-9a5a-6e205c8be63f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fbc303d5-3dc6-54dd-9a5a-6e205c8be63f/attachment.md","path":"references/solo-maintainer-guide.md","size":8991,"sha256":"cb8a4f165c3d61e95dc419d4e65f9cf4e92266460cf12efdfb7c87d6a01155b7","contentType":"text/markdown; charset=utf-8"},{"id":"4c302fa5-c103-5081-b2bd-c0350387fa28","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4c302fa5-c103-5081-b2bd-c0350387fa28/attachment.md","path":"references/sonarcloud.md","size":17886,"sha256":"b21a24637dbbdd7748e8a05f02260914a8d91616f13cc9b3266e94745afe41d5","contentType":"text/markdown; charset=utf-8"},{"id":"f62e375b-b5c3-5321-a630-51acd7b73525","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f62e375b-b5c3-5321-a630-51acd7b73525/attachment.md","path":"references/test-invocation.md","size":10372,"sha256":"d5912721cf846461ea0ab8fbb93f4c6783bcb9f3981d5785b6aba7f3c16cffbb","contentType":"text/markdown; charset=utf-8"},{"id":"a34d5df0-a11f-5d12-b2ca-a06c61233716","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a34d5df0-a11f-5d12-b2ca-a06c61233716/attachment.md","path":"references/tier-framing.md","size":2781,"sha256":"bf0a3d34b0b3f399b4b71d9003955e04602e560cd17ebd165d1b8860e4782d3a","contentType":"text/markdown; charset=utf-8"},{"id":"bc071d8b-d901-5d8c-8001-d61df1ab9a29","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bc071d8b-d901-5d8c-8001-d61df1ab9a29/attachment.sh","path":"scripts/add-spdx-headers.sh","size":2984,"sha256":"26fc9cd300f1ba5d93b1b0aad5860c7c0888cb44ff9c35f94671ac7033702835","contentType":"application/x-sh; charset=utf-8"},{"id":"71c715d8-c303-51e0-8046-7a61630cd9ff","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/71c715d8-c303-51e0-8046-7a61630cd9ff/attachment.sh","path":"scripts/analyze-bus-factor.sh","size":3224,"sha256":"26a99ded9409bc6461a6d846f70f43bdadb1d1d536010f250586c537d4a00634","contentType":"application/x-sh; charset=utf-8"},{"id":"a1d7b8b4-ea51-5052-92d4-0eb3e074fdd1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a1d7b8b4-ea51-5052-92d4-0eb3e074fdd1/attachment.sh","path":"scripts/check-branch-coverage.sh","size":4381,"sha256":"dc6910e59af5c5373a52fe01633ae72d16008cb162249749fdc232af3e15e65e","contentType":"application/x-sh; charset=utf-8"},{"id":"a2cbad24-110c-58b2-9792-73cf2eea4028","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a2cbad24-110c-58b2-9792-73cf2eea4028/attachment.sh","path":"scripts/check-coverage-threshold.sh","size":2132,"sha256":"08556074cdfceedf54bcfd7fa48468d35c99bd29f9b33918886c7bc42740dbfe","contentType":"application/x-sh; charset=utf-8"},{"id":"d4a45f68-dd0c-5cb9-96e6-92e7c7032a32","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d4a45f68-dd0c-5cb9-96e6-92e7c7032a32/attachment.sh","path":"scripts/check-tls-minimum.sh","size":6043,"sha256":"9cd7d643b885b70854c15e5c32cd43410cb977259e799aa6bd0785167bb2f3c6","contentType":"application/x-sh; charset=utf-8"},{"id":"d9e88ff3-3744-5c31-9997-6856eb901ee4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d9e88ff3-3744-5c31-9997-6856eb901ee4/attachment.py","path":"scripts/submit-badges.py","size":10707,"sha256":"b5cba6428ddb7e4f3d4f4f127df613c6f06e8d65c770e8c19c11407757ead5a4","contentType":"text/x-python; charset=utf-8"},{"id":"44ae313d-bf49-5ca6-9f70-a5f158ac5e47","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/44ae313d-bf49-5ca6-9f70-a5f158ac5e47/attachment.sh","path":"scripts/verify-badge-criteria.sh","size":5342,"sha256":"9d47b8d6e01f3bb5b08cc2c0f882f067afb5e4ecd66da9797b3d67ab3d4d3ecd","contentType":"application/x-sh; charset=utf-8"},{"id":"c027e791-8d44-52ff-9b6e-0321d61fc303","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c027e791-8d44-52ff-9b6e-0321d61fc303/attachment.sh","path":"scripts/verify-reproducible-build.sh","size":4310,"sha256":"fe586568dc3301cfecb63b874db54b7334b38e68e6171d27f350d81beee1f7e3","contentType":"application/x-sh; charset=utf-8"},{"id":"3e3f8a5f-ef54-5ee8-88f0-6fd8300eea29","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3e3f8a5f-ef54-5ee8-88f0-6fd8300eea29/attachment.sh","path":"scripts/verify-review-requirements.sh","size":5746,"sha256":"a6e44d1f3d2a191e659700fc638ea3f679ec9341b9fdfca0f71a0edaa47e08b8","contentType":"application/x-sh; charset=utf-8"},{"id":"c5365897-a94a-5790-9ecb-a3833f7cae09","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c5365897-a94a-5790-9ecb-a3833f7cae09/attachment.sh","path":"scripts/verify-signed-tags.sh","size":3761,"sha256":"a3b93cc781c622de710bddbbebde7538fdf65c8e88361397ba3a9be50a9d8acf","contentType":"application/x-sh; charset=utf-8"},{"id":"68637ada-3dda-50ee-bde8-472154c5b35b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/68637ada-3dda-50ee-bde8-472154c5b35b/attachment.sh","path":"scripts/verify-spdx-headers.sh","size":3115,"sha256":"b2ef711696b16a1906fc947646eb952c4a367b117a5352de5d95f5a139641125","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"bcffe60cc501367be401e182fb103fb897ec65db12589018e2b9c4cac7185b23","attachment_count":44,"text_attachments":44,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/enterprise-readiness/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"security","import_tag":"clean-skills-v1","description":"Use when evaluating projects for production or enterprise readiness, implementing supply chain security (SLSA, cosign, SBOMs, pnpm), hardening CI/CD pipelines, establishing quality gates (TYPO3: CI matrix PHP 8.2-8.5 x TYPO3 12.4/13.4/14.3 LTS), pursuing OpenSSF Best Practices Badge (Passing/Silver/Gold) or OSPS Baseline levels, reviewing code quality, writing ADRs, or configuring Git hooks and CI pipelines."}},"renderedAt":1782981131562}

Enterprise Readiness Assessment Production/enterprise tier only — see . When to Use - Production/enterprise readiness evaluations - Supply chain security: SLSA provenance, cosign signing, SBOMs - CI/CD hardening, workflow permissions - OpenSSF Best Practices (Passing/Silver/Gold), OSPS Baseline (L1/2/3) - Scorecard optimization (Token-Permissions, Branch-Protection, Pinned-Deps) - Code review, ADRs, changelogs, SECURITY.md Assessment Workflow 1. Discovery : Identify platform, languages, existing CI/CD, dependabot.yml 2. Scoring : Apply checklists; check Scorecard, badge criteria, coverage 3.…