Repository navigation
docs: add post-MVP backlog tasks and milestone - #1
Merged
Merged
Conversation
Add milestone m-1 (Post-MVP Hardening) and TASK-10 through TASK-20 covering reliability fixes, CI gaps, advisory coverage, and feed and site improvements identified after the MVP shipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
Add a shared `checks` job running ruff and pytest, which build-index and update-feed now depend on. Add a pull_request trigger so PRs are checked; only `checks` runs on a PR, with build-index, update-feed and deploy all skipping. Scope permissions to match: top-level drops to contents:read, with write granted only to the jobs that push and pages/id-token only to deploy. Fix a pre-existing test failure this exposed: generate_feed emitted en dashes where the tests and design spec both specify em dashes. The suite had never run in CI so the drift went unnoticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
Fetching made one unpaginated request with no timeout and no retries, so GitHub's default per_page=30 silently truncated the feed for every busy repo — six of twelve were pinned at exactly 30. Request per_page=100 and follow the Link rel="next" URL until exhausted, bounded by MAX_PAGES. Pass an explicit (connect, read) timeout on every request. Retry 429/5xx and connection errors with exponential backoff over four attempts, honouring Retry-After; 404 and other client errors fail immediately rather than backing off. Add tests/test_integration.py serving the endpoint over real HTTP with real Link headers, since mocking requests.get stubs out the Link parsing that pagination depends on. It retrieves 250 advisories across 3 pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
A single failing repo propagated out of main()'s loop and aborted the whole run, so one renamed or private entry in config.yaml silently froze the published feed. Extract collect_advisories(), which catches per-repo failures, records them, and continues. Add logging throughout — per-repo counts, per-repo errors, and a run summary. main() now returns an exit code, non-zero only when every repo failed, and returns before writing in that case so the existing feed is left intact rather than replaced with an empty one. Zero advisories across succeeding repos stays exit 0 with a warning; the current config already has a repo that legitimately returns none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The feed kept everything forever — 233 items and 588 KB back to 2019, all re-downloaded on every subscriber poll — and retracted advisories never left it. A null published_at would also raise TypeError while sorting. Drop advisories with a truthy withdrawn_at (checked truthily, since the API sends withdrawn_at: null on live ones). Add an optional feed: config section with max_items and max_age_days, defaulting to 100 items and no age limit so existing fork configs are unaffected; both apply after sorting so the newest survive. Guard the sort key so null, missing, or unparseable timestamps sort last with a warning instead of raising. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The API returns cve_id, cvss, cwes and per-package version ranges; none of it reached the feed. Add a metadata block ahead of each advisory body, the CVE in the item title, and category elements for severity and repo. Kept as markdown rather than HTML to match the body GitHub already returns, avoiding a conversion dependency. Every field is optional and inconsistently shaped, so CVSS falls back through cvss_severities, patched versions are read from either patched_versions or first_patched_version, and an advisory with none of them renders as just its body. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
main() hardcoded the only source with an if/elif-style guard. Extract the sources/ package the design spec called for: http.py holds the shared retry, timeout and pagination plumbing, github.py holds repository advisories, and __init__.py holds the registry plus the normalised advisory shape every source must produce. Sources return a SourceResult rather than a bare list, so each isolates failures across its own targets and TASK-12's per-repo isolation survives having several sources. An unsupported source: value now logs an error naming it instead of being silently skipped. Extend the CLI with --config, --output, --dry-run and --verbose. Output-neutral: rendered through the real config with a 274-advisory fixture, before and after are byte-identical excluding lastBuildDate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The repository endpoint returns only advisories a project filed in its own security tab. Anything filed against its published packages by third parties, ecosystem maintainers or CVE assigners was invisible. Add sources/ghsa.py querying GET /advisories with ecosystem and affects filters — REST rather than GraphQL, since it reuses the existing Link-header pagination unchanged. Normalise the payload by setting the package as the origin label; the shape otherwise already matches. Extend dedup beyond GHSA ID: one vulnerability can now arrive from both sources under different GHSA IDs, so collapse by CVE too, keeping whichever record scores higher on populated detail. Withdrawn records are filtered before dedup so a retracted richer record cannot win a CVE and vanish. A misspelled package returns an empty list rather than a 404, so log a warning naming it instead of silently contributing nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The published page listed repo names and linked the feed but showed no advisories, had no RSS autodiscovery, and was built from a 70-line f-string with every CSS brace doubled. Move the markup to templates/index.html rendered with string.Template (no new dependency). Render each advisory with a severity badge, origin, linked summary, date, CVE and CVSS, above a last-updated line. Add the alternate link tag, a dark-mode palette and a mobile breakpoint. All advisory text is escaped. Fix two problems this surfaced: --index-only runs on every push and would have wiped the advisory list, so it now recovers the published set from the committed feed; and RSS pubDate is RFC 2822, which _published_at could not parse, so every round-tripped date read as unknown. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
The deploy job checked out main and pulled to pick up the commit the build job had just pushed, racing the content it was meant to publish. Upload public/ as a Pages artifact from whichever job generated it and reduce deploy to a single deploy-pages step, so what deploys is what was built. Add a concurrency group so a manual dispatch overlapping the nightly run serialises instead of failing on a non-fast-forward push. Correct CLAUDE.md, which claimed public/feed.xml was gitignored — it is tracked and committed daily. Keep committing it: --index-only reads it to rebuild the page without fetching, and it is the fallback if a run fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
Nothing was bumping the exact-pinned requirements or the pinned action versions, in a project whose purpose is tracking vulnerabilities. Add weekly Dependabot for pip and github-actions, and a CodeQL workflow for Python on push, PR and weekly. Add mypy to the CI checks job, scoped to vulnfeed.py and sources/ — running it over tests produced 45 ElementTree union-attr errors of no diagnostic value. It found one real issue: get_source passed a possibly-None key to dict.get. Extend ruff with bugbear and pyupgrade, both already clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add milestone m-1 (Post-MVP Hardening) and TASK-10 through TASK-20
covering reliability fixes, CI gaps, advisory coverage, and feed and
site improvements identified after the MVP shipped.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_012tLHWDf5uusrUam2PUXufB