diff --git a/.github/workflows/checklist-score.yml b/.github/workflows/checklist-score.yml new file mode 100644 index 0000000..692a725 --- /dev/null +++ b/.github/workflows/checklist-score.yml @@ -0,0 +1,256 @@ +name: Best Practices + +on: + push: + paths: + - 'BestPracticesChecklist.md' + schedule: + - cron: '0 9 * * 1' # Every Monday 9am UTC (for criteria sync) + workflow_dispatch: + +jobs: + + # Runs when BestPracticesChecklist.md changes + update-score: + runs-on: ubuntu-latest + permissions: + contents: write + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Parse checklist and compute score + run: | + python3 << 'EOF' + import json, re + from datetime import date + + CATEGORY_HEADERS = { + "## 🏗️ Basics": "basics", + "## 🔄 Change Control": "change_control", + "## 🐛 Reporting": "reporting", + "## ✅ Quality": "quality", + "## 🔐 Security": "security", + "## 🔬 Analysis": "analysis", + } + + CATEGORY_TOTALS = { + "basics": 8, + "change_control": 6, + "reporting": 8, + "quality": 11, + "security": 9, + "analysis": 7, + } + + with open("BestPracticesChecklist.md") as f: + content = f.read() + + lines = content.splitlines() + current_cat = None + counts = {k: 0 for k in CATEGORY_TOTALS} + + for line in lines: + stripped = line.strip() + for header, cat in CATEGORY_HEADERS.items(): + if stripped == header: + current_cat = cat + break + # [x] = Met, [~] = N/A (counts as met) + if current_cat and re.match(r'- \[[xX~]\]', stripped): + counts[current_cat] += 1 + + total_met = sum(counts.values()) + total = sum(CATEGORY_TOTALS.values()) + percent = round((total_met / total) * 100) if total > 0 else 0 + + if percent >= 80: + color = "brightgreen" + elif percent >= 60: + color = "yellow" + elif percent >= 40: + color = "orange" + else: + color = "red" + + status = { + "schemaVersion": 1, + "label": "Best Practices", + "message": f"{percent}%", + "schema": "aossie-best-practices-v1", + "updated": str(date.today()), + "met": total_met, + "total": total, + "percent": percent, + "color": color, + "categories": { + cat: {"met": counts[cat], "total": CATEGORY_TOTALS[cat]} + for cat in CATEGORY_TOTALS + } + } + + with open("checklist-status.json", "w") as f: + json.dump(status, f, indent=2) + + print(f"Score: {total_met}/{total} ({percent}%)") + EOF + + - name: Update score table in checklist + run: | + python3 << 'EOF' + import json, re + + with open("checklist-status.json") as f: + s = json.load(f) + + def emoji(met, total): + pct = met / total * 100 if total else 0 + return "✅" if pct == 100 else ("🟡" if pct >= 50 else "🔴") + + c = s["categories"] + table = f"""| Category | Met | Total | Status | + |--------------------|-----|-------|--------| + | Basics | {c['basics']['met']} | {c['basics']['total']} | {emoji(c['basics']['met'], c['basics']['total'])} | + | Change Control | {c['change_control']['met']} | {c['change_control']['total']} | {emoji(c['change_control']['met'], c['change_control']['total'])} | + | Reporting | {c['reporting']['met']} | {c['reporting']['total']} | {emoji(c['reporting']['met'], c['reporting']['total'])} | + | Quality | {c['quality']['met']} | {c['quality']['total']} | {emoji(c['quality']['met'], c['quality']['total'])} | + | Security | {c['security']['met']} | {c['security']['total']} | {emoji(c['security']['met'], c['security']['total'])} | + | Analysis | {c['analysis']['met']} | {c['analysis']['total']} | {emoji(c['analysis']['met'], c['analysis']['total'])} | + | **Total** | **{s['met']}** | **{s['total']}** | **{s['percent']}%** |""" + + with open("BestPracticesChecklist.md") as f: + content = f.read() + + marker = "" + if marker not in content: + print("⚠️ Warning: Auto-update marker not found in BestPracticesChecklist.md") + + new_content = re.sub( + r'(?<=\n).*?(?=\n---)', + table.strip(), + content, + flags=re.DOTALL + ) + + if new_content == content: + print("⚠️ Warning: Table was not updated - check marker format") + + with open("BestPracticesChecklist.md", "w") as f: + f.write(new_content) + EOF + + - name: Commit updated files + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add checklist-status.json BestPracticesChecklist.md + if ! git diff --staged --quiet; then + git commit -m "chore: update best practices score [skip ci]" + git fetch origin ${{ github.ref_name }} + if ! git rebase origin/${{ github.ref_name }}; then + git rebase --abort + exit 1 + fi + git push + fi + + # Fetches OpenSSF criteria → diffs → opens PR if changed (Runs ONLY in template-repo on schedule) + sync-criteria: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + if: | + github.repository == 'AOSSIE-Org/Template-Repo' && + (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') + steps: + - uses: actions/checkout@v4 + + - name: Fetch upstream OpenSSF criteria + run: | + curl -sf \ + "https://raw.githubusercontent.com/coreinfrastructure/best-practices-badge/main/docs/criteria/criteria.md" \ + -o upstream_criteria.md || \ + curl -sf \ + "https://raw.githubusercontent.com/coreinfrastructure/best-practices-badge/main/docs/criteria.md" \ + -o upstream_criteria.md || { + echo "::error::Failed to fetch upstream OpenSSF criteria from both URLs" + exit 1 + } + + - name: Ensure maintenance label exists + run: gh label create maintenance --description "Maintenance tasks" --color "FBCA04" || true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Diff upstream vs checklist and open PR if changed + run: | + python3 << 'EOF' + import re, subprocess, sys + from datetime import date + + with open("upstream_criteria.md") as f: + upstream_content = f.read() + + with open("BestPracticesChecklist.md") as f: + local_content = f.read() + + upstream_ids = set(re.findall(r'\[([a-z][a-z0-9_]+)\]\(#\1\)', upstream_content)) + local_ids = set(re.findall(r'\*\*([a-z][a-z0-9_]+)\*\*', local_content)) + + if not upstream_ids: + print("⚠️ Warning: No criteria IDs found in upstream. Format may have changed.") + sys.exit(0) + + added = upstream_ids - local_ids + removed = local_ids - upstream_ids + + if not added and not removed: + print("✅ In sync with upstream. No PR needed.") + sys.exit(0) + + lines = ["# OpenSSF Criteria Sync Report\n"] + if added: + lines.append("## 🆕 New criteria to add to checklist\n") + lines += [f"- `{c}`" for c in sorted(added)] + if removed: + lines.append("\n## ❌ Criteria removed from OpenSSF (review checklist)\n") + lines += [f"- `{c}`" for c in sorted(removed)] + + report = "\n".join(lines) + print(report) + + branch = f"chore/openssf-sync-{date.today()}" + subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True) + subprocess.run(["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], check=True) + subprocess.run(["git", "checkout", "-b", branch], check=True) + + with open(".github/openssf_criteria_diff.md", "w") as f: + f.write(report) + + subprocess.run(["git", "add", ".github/openssf_criteria_diff.md"], check=True) + subprocess.run(["git", "commit", "-m", f"chore: OpenSSF criteria diff {date.today()}"], check=True) + subprocess.run(["git", "push", "origin", branch], check=True) + + # Check if PR already exists for this branch + result = subprocess.run( + ["gh", "pr", "list", "--head", branch, "--json", "number"], + capture_output=True, text=True + ) + if result.returncode == 0 and result.stdout.strip() != "[]": + print(f"PR already exists for branch {branch}, skipping creation") + sys.exit(0) + + subprocess.run([ + "gh", "pr", "create", + "--title", "🔄 OpenSSF Criteria Update - Action Required", + "--body", report, + "--base", "main", + "--head", branch, + "--label", "maintenance" + ], check=True) + EOF + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/BestPracticesChecklist.md b/BestPracticesChecklist.md new file mode 100644 index 0000000..aed888b --- /dev/null +++ b/BestPracticesChecklist.md @@ -0,0 +1,258 @@ +# AOSSIE Best Practices Checklist + +> Criteria adapted from the [OpenSSF Best Practices Badge](https://github.com/coreinfrastructure/best-practices-badge) +> (MIT / CC BY 3.0) by OpenSSF contributors. Modified for AOSSIE multi-repo template use. + +> **Purpose:** Covers OpenSSF Best Practices criteria that are NOT auto-detected by OpenSSF Scorecard. +> Scorecard already handles: License, SAST tools, CI tests, Security Policy file, Branch Protection, +> Pinned Dependencies, Signed Releases, Maintained status, and Known Vulnerabilities. +> +> **How to use:** +> 1. Fill in checkboxes below — tick `[x]` for Met, leave `[ ]` for Unmet, use `[~]` for N/A +> 2. Add a brief note or URL after each item as evidence +> 3. Run the checklist-score workflow to update the badge automatically +> +> **Legend:** +> - 🔴 MUST — Required for passing +> - 🟡 SHOULD — Required unless documented rationale given +> - 🔵 SUGGESTED — Optional but recommended +> - ⚪ N/A — Mark `[~]` if not applicable, add justification + +--- + +## Score Summary + + +| Category | Met | Total | Status | +|--------------------|-----|-------|--------| +| Basics | 0 | 8 | 🔴 | +| Change Control | 0 | 6 | 🔴 | +| Reporting | 0 | 8 | 🔴 | +| Quality | 0 | 10 | 🔴 | +| Security | 0 | 9 | 🔴 | +| Analysis | 0 | 7 | 🔴 | +| **Total** | **0** | **48** | **0%** | +--- + +## 🏗️ Basics + +### Project Website & Documentation + +- [ ] 🔴 **description_good** — The project README/website clearly describes what the software does and what problem it solves. + - *Evidence URL:* + +- [ ] 🔴 **interact** — The project provides information on how to obtain the software, submit bug reports, and contribute. + - *Evidence URL:* + +- [ ] 🔴 **contribution** — `CONTRIBUTING.md` explains the contribution process (e.g., PRs are used, how to open one). + - *Evidence URL:* + +- [ ] 🟡 **contribution_requirements** — `CONTRIBUTING.md` references acceptable contribution standards (coding style, tests required, etc.). + - *Evidence URL:* + +- [ ] 🔴 **documentation_basics** — Basic documentation exists for the software (README, Wiki, or docs folder). + - *Evidence URL:* `[ ]` N/A — *Justification:* + +- [ ] 🔴 **documentation_interface** — Reference documentation describes the external interface (API inputs/outputs, CLI flags, config schema, etc.). + - *Evidence URL:* `[ ]` N/A — *Justification:* + +### Other Basics + +- [ ] 🔴 **discussion** — Project has a searchable, URL-addressable discussion mechanism (GitHub Issues, Discord with archive, mailing list, etc.) that doesn't require proprietary client software. + - *Evidence URL:* + +- [ ] 🟡 **english** — Documentation is provided in English and English bug reports/comments are accepted. + - *Note:* + +--- + +## 🔄 Change Control + +### Version Control + +- [ ] 🔵 **repo_distributed** — Project uses a distributed VCS (e.g., git). *(SUGGESTED)* + - *Evidence URL:* + +### Version Numbering + +- [ ] 🔴 **version_unique** — Each release has a unique version identifier (e.g., v1.0.0). + - *Evidence URL:* + +- [ ] 🔵 **version_semver** — Project uses [SemVer](https://semver.org) or [CalVer](https://calver.org/) format. *(SUGGESTED)* + - *Note:* + +- [ ] 🔵 **version_tags** — Releases are tagged in the VCS (e.g., `git tag v1.0.0`). *(SUGGESTED)* + - *Evidence URL:* + +### Release Notes + +- [ ] 🔴 **release_notes** — Each release includes human-readable release notes summarizing major changes. Raw `git log` output is NOT acceptable. + - *Evidence URL:* `[ ]` N/A — *Justification (continuous delivery / no external reuse):* + +- [ ] 🔴 **release_notes_vulns** — Release notes identify every publicly known vulnerability (with CVE) fixed in that release. + - *Evidence URL:* `[ ]` N/A — *Justification (no publicly known vulns / users can't self-update):* + +--- + +## 🐛 Reporting + +### Bug Reporting + +- [ ] 🔴 **report_process** — A bug-reporting process exists (e.g., GitHub Issues link in README). + - *Evidence URL:* + +- [ ] 🟡 **report_tracker** — An issue tracker (e.g., GitHub Issues) is used to track individual bugs. + - *Evidence URL:* + +- [ ] 🔴 **report_responses** — A majority of bug reports submitted in the last 2–12 months have been acknowledged (response ≠ fix). + - *Self-certification note:* + +- [ ] 🟡 **enhancement_responses** — More than 50% of enhancement requests in the last 2–12 months have received a response. + - *Self-certification note:* + +- [ ] 🔴 **report_archive** — Reports and responses are publicly archived and searchable (GitHub Issues satisfies this). + - *Evidence URL:* + +### Vulnerability Reporting + +- [ ] 🔴 **vulnerability_report_process** — A vulnerability reporting process is documented (e.g., `SECURITY.md`). + - *Evidence URL:* + +- [ ] 🟡 **vulnerability_report_private** — If private vulnerability reporting is supported, the method for private submission is documented. + - *Evidence URL:* `[ ]` N/A — *Justification:* + +- [ ] 🔴 **vulnerability_report_response** — Initial response to any vulnerability report received in the last 6 months was within 14 days. + - *Self-certification note:* `[ ]` N/A — *Justification (no reports received):* + +--- + +## ✅ Quality + +### Build System + +- [ ] 🔴 **build** — If the project requires building, a working build system exists that can auto-rebuild from source. + - *Evidence URL:* `[ ]` N/A — *Justification (interpreted language / no build step):* + +- [ ] 🔵 **build_common_tools** — Common build tools are used (npm, pip, cargo, make, gradle, etc.). *(SUGGESTED)* + - *Evidence URL:* `[ ]` N/A + +- [ ] 🟡 **build_floss_tools** — The project can be built using only FLOSS tools. + - *Note:* `[ ]` N/A + +### Automated Testing + +- [ ] 🔵 **test_invocation** — The test suite can be invoked in a standard way for the language (e.g., `npm test`, `pytest`, `cargo test`). *(SUGGESTED)* + - *Evidence URL:* + +- [ ] 🔵 **test_most** — The test suite covers most code branches, input fields, and functionality. *(SUGGESTED)* + - *Estimated coverage %:* + +### New Functionality Testing Policy + +- [ ] 🔴 **test_policy** — The project has a general policy that new functionality must include tests in the automated test suite. + - *Evidence (CONTRIBUTING reference or informal policy):* + +- [ ] 🔴 **tests_are_added** — Evidence exists that the test policy has been followed in recent major changes (e.g., PRs include tests). + - *Evidence URL (recent PR with tests):* + +- [ ] 🔵 **tests_documented_added** — The test policy is documented in contribution instructions. *(SUGGESTED)* + - *Evidence URL:* + +### Linting / Warning Flags + +- [ ] 🔴 **warnings** — At least one linter or compiler warning flag is enabled (ESLint, Pylint, clippy, golangci-lint, Slither for Solidity, etc.). + - *Tool used:* + +- [ ] 🔴 **warnings_fixed** — Warnings from the linter are addressed (not suppressed without reason). + - *Note:* + +- [ ] 🔵 **warnings_strict** — Project uses maximum strictness in linter config where practical. *(SUGGESTED)* + - *Note:* + +--- + +## 🔐 Security + +### Secure Development Knowledge + +- [ ] 🔴 **know_secure_design** — At least one primary developer knows how to design secure software (familiar with OWASP, threat modeling, secure-by-default principles). + - *Self-certification note:* + +- [ ] 🔴 **know_common_errors** — At least one primary developer knows common vulnerability types for this software's category and how to mitigate them (e.g., injection, XSS, reentrancy for Solidity, prompt injection for AI). + - *Self-certification note:* + +### Cryptography (mark N/A if project does not handle cryptography) + +- [ ] 🔴 **crypto_published** — Only publicly reviewed cryptographic protocols/algorithms are used by default. + - *Note:* `[ ]` N/A + +- [ ] 🟡 **crypto_call** — Project calls an established crypto library rather than reimplementing crypto functions. + - *Library used:* `[ ]` N/A + +- [ ] 🔴 **crypto_working** — No broken algorithms (MD4, MD5, single DES, RC4, Dual_EC_DRBG) used unless required for interoperability (must be documented). + - *Note:* `[ ]` N/A + +- [ ] 🔴 **crypto_keylength** — Key lengths meet [NIST 2030 minimums](https://www.keylength.com/en/4/) by default. + - *Note:* `[ ]` N/A + +- [ ] 🔴 **crypto_password_storage** — Passwords for external users are stored as iterated salted hashes (Argon2id, bcrypt, scrypt, PBKDF2). + - *Note:* `[ ]` N/A — *Justification (project doesn't store passwords):* + +- [ ] 🔴 **crypto_random** — Cryptographic keys and nonces are generated using a CSPRNG; insecure generators (Math.random, rand()) are NOT used for security purposes. + - *Note:* `[ ]` N/A + +- [ ] 🟡 **delivery_unsigned** — Cryptographic hashes are NOT retrieved over plain HTTP without a signature check. + - *Note:* + +--- + +## 🔬 Analysis + +### Static Code Analysis + +- [ ] 🔴 **static_analysis_fixed** — All medium+ severity vulnerabilities found by static analysis are fixed in a timely manner after confirmation. + - *Note:* `[ ]` N/A + +- [ ] 🔵 **static_analysis_common_vulnerabilities** — The static analysis tool includes checks for common vulnerabilities in the language/environment (e.g., eslint-plugin-security, bandit, Slither). *(SUGGESTED)* + - *Tool + ruleset:* `[ ]` N/A + +- [ ] 🔵 **static_analysis_often** — Static analysis runs on every commit or at least daily (CI integration). *(SUGGESTED)* + - *Evidence URL:* `[ ]` N/A + +### Dynamic Code Analysis + +- [ ] 🔵 **dynamic_analysis** — At least one dynamic analysis tool is applied before major releases (fuzzer, web app scanner like OWASP ZAP, etc.). *(SUGGESTED)* + - *Tool used:* `[ ]` N/A — *Justification:* + +- [ ] 🔵 **dynamic_analysis_enable_assertions** — Dynamic analysis / testing runs with assertions enabled (not just production mode). *(SUGGESTED)* + - *Note:* + +- [ ] 🔴 **dynamic_analysis_fixed** — Medium+ severity vulnerabilities found by dynamic analysis are fixed in a timely manner. + - *Note:* `[ ]` N/A + +- [ ] 🔵 **dynamic_analysis_unsafe** — If the project uses memory-unsafe languages (C/C++), memory safety tools (Valgrind, AddressSanitizer) are used. *(SUGGESTED)* + - *Note:* `[ ]` N/A — *Justification (project uses memory-safe languages):* + +--- + +## 📎 Project-Specific Notes + +> Add domain-specific notes here for Web3, Full-Stack, or AI projects. + +### Web3 / Solidity Notes +- Scorecard does not audit Solidity-specific security. Use [Slither](https://github.com/crytic/slither) for `static_analysis` and `warnings` criteria. +- For `crypto_*` criteria, document which cryptographic primitives your contracts rely on (e.g., ECDSA in EVM is standard). +- Smart contract audit reports count as evidence for `know_secure_design`. + +### Full-Stack / Next.js Notes +- For `crypto_password_storage`: document which auth library handles hashing (e.g., NextAuth + bcrypt). +- For `dynamic_analysis`: [OWASP ZAP](https://www.zaproxy.org/) can be run as a GitHub Action. + +### AI / LLM Notes +- For `know_common_errors`: include awareness of prompt injection, data leakage, and model output validation. +- For `dynamic_analysis`: consider adversarial input testing as a form of dynamic analysis. + +--- + +*This checklist complements [OpenSSF Scorecard](https://scorecard.dev/) (auto-detected checks) and is +inspired by the [OpenSSF Best Practices Badge](https://www.bestpractices.dev/en/criteria/0) passing criteria.* \ No newline at end of file diff --git a/README.md b/README.md index 1de0032..91aecbb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Youtube Badge + +[![Best Practices](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F%7Bowner%7D%2F%7Brepo%7D%2Fmain%2Fchecklist-status.json&logo=openssf)](./BestPracticesChecklist.md)

--- diff --git a/checklist-status.json b/checklist-status.json new file mode 100644 index 0000000..2c7d6a6 --- /dev/null +++ b/checklist-status.json @@ -0,0 +1,37 @@ +{ + "schemaVersion": 1, + "label": "Best Practices", + "message": "0%", + "schema": "aossie-best-practices-v1", + "updated": "2026-03-29", + "met": 0, + "total": 48, + "percent": 0, + "color": "red", + "categories": { + "basics": { + "met": 0, + "total": 8 + }, + "change_control": { + "met": 0, + "total": 6 + }, + "reporting": { + "met": 0, + "total": 8 + }, + "quality": { + "met": 0, + "total": 10 + }, + "security": { + "met": 0, + "total": 9 + }, + "analysis": { + "met": 0, + "total": 7 + } + } +} \ No newline at end of file