Skip to content

feat(vuln): OSV cache staleness + refresh flags — closes #30#44

Closed
Wolfvin wants to merge 5 commits into
mainfrom
feat/issue-30-vuln-staleness
Closed

feat(vuln): OSV cache staleness + refresh flags — closes #30#44
Wolfvin wants to merge 5 commits into
mainfrom
feat/issue-30-vuln-staleness

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #30. Completes the last Phase 1 (#21) checklist item: "Fix vuln DB staleness (OSV.dev API, update scheduler)".

vuln-scan now reports OSV cache freshness and lets agents force fresh data. Previously the 24h OSV cache had no staleness indicator and no way to force a re-fetch short of deleting the cache — agents relying on vuln-scan for security posture had no way to know whether the CVE results were minutes or 23 hours old.

The background refresh scheduler (stretch goal #4 in the issue) is intentionally out of scope for Phase 1.

Deliverables (3 of 3)

1. cache_info in vuln-scan JSON output (additive)

"cache_info": {
  "last_refresh": "2026-06-28T10:00:00Z",
  "age_hours": 23.5,
  "ttl_hours": 24,
  "is_stale": false,
  "stale_packages": ["requests@2.20.0"]
}

is_stale is true when any cached entry is past the effective TTL, the newest entry's age ≥ the effective TTL, or there are OSV-queriable packages but none are cached (no fresh coverage). stale_packages lists the "name@version" of cached entries past the effective TTL. A static cache_info block is also surfaced on the no-packages and OSV-failure paths so consumers can rely on the shape.

2. vuln-scan --refresh

Bypasses the OSV cache and forces fresh OSV.dev API calls for every package, then updates the cache with the new results (the existing _batch_query_api already cache.sets each response). No-op in --offline mode.

3. vuln-scan --max-age Nh

Treats cache entries older than N hours as stale for this run only (overrides the default 24h TTL without changing the stored TTL). Accepts 6h / 30m / 2d / 90s / bare-integer (hours). cache_info.ttl_hours and the staleness threshold honour this override so they stay consistent. Ignored with --refresh and in --offline mode. Invalid values return an error result instead of crashing.

Files

File Change
scripts/osv_client.py New OSVCache.peek(key) (pure inspection, no mutation). New OSVClient.get_cache_info(packages, max_age=None). OSVClient.query_packages() gains force_refresh + max_age (default behaviour unchanged).
scripts/vulnscan_engine.py scan_vulnerabilities() gains refresh + max_age params; attaches cache_info to result.
scripts/commands/vuln_scan.py New --refresh + --max-age flags + _parse_max_age() helper.
tests/test_vuln_staleness.py New — 36 network-free tests.
README.md, SKILL-QUICK.md, CHANGELOG.md Docs (additive).

Constraints honoured

  • Python 3.8+, no new deps.
  • cache_info is additive — no existing vuln-scan output field changed.
  • Network calls only when --refresh is set, cache expired, or --max-age marks entries stale. Default behaviour unchanged.
  • Did not touch scan.py or graph_model.py (worker 4-b's domain on feat/issue-25-incremental-graph).
  • Worked in an isolated git worktree (CodeLens-4a) so the shared checkout used by worker 4-b was undisturbed.

Commits (incremental)

  1. f942088 feat(vuln): cache_info in output
  2. 82b94ed feat(vuln): --refresh flag
  3. d18e830 feat(vuln): --max-age flag
  4. 81e93c4 test(vuln): staleness tests
  5. db3cac3 docs(vuln): README + SKILL-QUICK + CHANGELOG

Tests

PYTHONPATH=scripts python3 -m pytest tests/test_vuln_staleness.py -v
  → 36 passed

PYTHONPATH=scripts python3 -m pytest tests/ --ignore=tests/test_integration.py
  → 685 passed, 12 skipped, 0 failures

Baseline on main was 649 passed / 12 skipped; the +36 are the new tests. No new failures, no regressions. (test_integration.py is excluded per repo convention — it is OOM-heavy.)

End-to-end CLI confirmation: codelens vuln-scan <ws> --format json now includes a top-level cache_info block; --refresh and --max-age 6h parse and run; an invalid --max-age 6x returns {status: error, error: invalid_argument, message: ...} instead of crashing.

Acceptance criteria (from issue #30)

  • vuln-scan output includes cache_info with last_refresh, age_hours, is_stale, stale_packages (also ttl_hours).
  • --refresh forces fresh API calls and updates the cache.
  • --max-age overrides TTL for the current run.
  • Tests: cache_info populated, --refresh bypasses cache, --max-age treats fresh entries as stale.
  • Docs: README + SKILL-QUICK mention the new flags; CHANGELOG entry under [8.2.0].

worker-4-b added 5 commits June 28, 2026 07:59
Issue #30 — expose OSV cache freshness in vuln-scan JSON output.

- OSVCache.peek(key): pure inspection returning (timestamp, ttl) without
  mutating the cache (unlike get(), which deletes expired entries).
- OSVClient.get_cache_info(packages, max_age=None): returns
  {last_refresh, age_hours, ttl_hours, is_stale, stale_packages}.
  is_stale when any cached entry is past the effective TTL, the newest
  entry's age >= TTL, or there are OSV-queriable packages but none cached.
- vulnscan_engine.scan_vulnerabilities: attach cache_info to the result
  dict (additive — no existing output fields changed). Static cache_info
  blocks are surfaced on the no-packages and OSV-failure paths so
  consumers can rely on the shape.

Network calls unchanged: cache_info only inspects the local SQLite cache.
Issue #30 — 'codelens vuln-scan --refresh' bypasses the OSV cache and
forces fresh OSV.dev API calls for every package, then updates the cache
with the new results.

- OSVClient.query_packages(packages, force_refresh=False): when True (and
  not offline), skip cache reads and route all packages to _batch_query_api,
  which already caches each response via cache.set. No-op in offline mode.
- vulnscan_engine.scan_vulnerabilities(..., refresh=False): passes through
  as force_refresh to the OSV client.
- vuln-scan CLI: new --refresh flag (store_true).

Default behaviour unchanged: without --refresh the cache is consulted
first and only uncached packages hit the API.
Issue #30 — 'codelens vuln-scan --max-age Nh' treats OSV cache entries
older than N hours as stale for the current run only (overrides the
default 24h TTL without changing the stored TTL). Useful for agents that
want fresher data without a full --refresh.

- OSVClient.query_packages(packages, force_refresh=False, max_age=None):
  cached entries whose age exceeds max_age are re-fetched from the API for
  this run. Uses OSVCache.peek() to read timestamps without mutating the
  cache. Ignored when force_refresh is set (cache bypassed entirely) or in
  offline mode.
- OSVClient.get_cache_info(packages, max_age=None): ttl_hours and the
  staleness threshold now honour max_age so cache_info stays consistent
  with the run's effective TTL.
- vulnscan_engine.scan_vulnerabilities(..., max_age=None): passes through
  to both query_packages and get_cache_info.
- vuln-scan CLI: new --max-age flag + _parse_max_age() helper accepting
  '6h'/'30m'/'2d'/'90s'/bare-integer (hours). Invalid values return an
  error result instead of crashing.
Issue #30 — 36 new network-free tests in tests/test_vuln_staleness.py:

- OSVClient.get_cache_info(): shape, empty-cache staleness, fresh entry,
  expired entry listing, --max-age override, peek non-mutation,
  last_refresh = most-recent among multiple packages.
- OSVClient.query_packages(force_refresh=...): bypasses cache + calls API,
  default still hits cache, no-op in offline mode.
- OSVClient.query_packages(max_age=...): treats fresh entries as stale,
  below-threshold uses cache, ignored when force_refresh is set.
- _parse_max_age(): valid forms (6h/30m/90s/2d/bare/1.5h/whitespace/upper),
  None passthrough, invalid + zero/negative rejection.
- vuln-scan execute(): forwards refresh + parsed max_age to
  scan_vulnerabilities, returns error result on invalid --max-age.
- scan_vulnerabilities(): cache_info present with no deps, reflects seeded
  fresh cache, reports stale under --max-age, and existing output fields
  remain unchanged (additive).

The OSV API layer is mocked throughout; the SQLite cache is seeded
directly with backdated timestamps.
Issue #30 — document the new vuln-scan staleness flags and the cache_info
output block.

- README.md: expand the vuln-scan command row with --refresh / --max-age
  and the cache_info block.
- SKILL-QUICK.md: add the flags + cache_info note to the Security command
  list.
- CHANGELOG.md: new 'OSV Cache Staleness Flag + Refresh Flags (issue #30)'
  section under [8.2.0] with Added / Changed / Non-Breaking subsections.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@Wolfvin Wolfvin closed this Jun 28, 2026
@Wolfvin Wolfvin deleted the feat/issue-30-vuln-staleness branch June 28, 2026 08:25
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
D Security Rating on New Code (required ≥ A)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] OSV cache auto-refresh scheduler + staleness flag in vuln-scan output

1 participant