Skip to content

feat(032): Security Advisories Check – spec, plan, tasks, and config#4263

Merged
ildyria merged 10 commits intomasterfrom
copilot/add-diagnostic-check-modal
Apr 6, 2026
Merged

feat(032): Security Advisories Check – spec, plan, tasks, and config#4263
ildyria merged 10 commits intomasterfrom
copilot/add-diagnostic-check-modal

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 6, 2026

Feature 032 – Security Advisories Check

PHP Backend

  • T-032-01: Config keys (urls.advisories.api_url, urls.advisories.cache_ttl, features.vulnerability-check)
  • T-032-02: app/DTO/SecurityAdvisory.php
  • T-032-03: app/Services/VersionRangeChecker.php + 22 unit tests
  • T-032-04: Extend ExternalRequestFunctions with array $extra_headers = []
  • T-032-05: app/Metadata/Json/AdvisoriesRequest.php (Accept: application/vnd.github+json)
  • T-032-06: tests/Fixtures/github-security-advisories.json
  • T-032-07: app/Services/SecurityAdvisoriesService.php + 10 unit tests
  • T-032-08: app/Actions/Diagnostics/Pipes/Checks/SecurityAdvisoriesCheck.php + 9 unit tests + registered in Errors.php
  • T-032-09: app/Http/Resources/Models/SecurityAdvisoryResource.php
  • T-032-10: Controller + IndexSecurityAdvisoriesRequest + route GET /api/v2/Security/Advisories + 6 feature tests

Frontend

  • T-032-11: resources/js/lychee.d.tsSecurityAdvisoryResource type
  • T-032-12: resources/js/services/security-advisories-service.ts
  • T-032-13: resources/js/components/modals/SecurityAdvisoriesModal.vue (modal only emits update:visible; composable owns sessionStorage)
  • T-032-14: resources/js/composables/modals/useAdvisoryModal.ts (singleton, documented) + integrated in Albums.vue

Quality Gates

  • PHPStan 0 errors, php-cs-fixer clean, 47 tests pass
  • npm run check 0 errors, npm run lint clean
Original prompt

Feature 32 - write the specifications, plan and tasks. Do not implement,

We want to add : a diagnostic check and a dismissable modal which pops in when the admin logs in to inform about vulnerabilities in their current version.

We should use a Service which query a url defined in #file:urls.php under the url.advisories.api_url key.
The activation or deactivation of the Vulnerability check should be set in #file:features.php with default activated.

The Diagnostic check should appear in the error section only if enabled and if user is currently logged in as admin. We do not want to disclose this information otherwise.

The default url for the security advisories check should be https://api.github.com/repos/LycheeOrg/Lychee/security-advisories
Note the required header "Accept: application/vnd.github+json"

For each vulnerability returned, we should validate if the current version of Lychee is vulnerable. This is done by comparing the value in vulnerable_version_range (note that it is comma separated). The error diagnostic should return the list of CVE and CVSS score.

Make a query to https://api.github.com/repos/LycheeOrg/Lychee/security-advisories for the json format of data.

The fetch of data should be cached for a configurable period of time set in url.advisories.cache_ttl in days.

The user has attached the following file paths as relevant context:

  • config/features.php
  • config/urls.php
  • .github/copilot-instructions.md
  • AGENTS.md

Created from VS Code.

Summary by CodeRabbit

Release Notes

  • New Features

    • Administrators can now view security vulnerabilities affecting their installation. When enabled, the system fetches relevant GitHub security advisories and displays them in a dismissible modal upon admin login, as well as in the diagnostics panel. Behavior is configurable via feature flag and environment variables.
  • Tests

    • Added comprehensive test coverage for security advisory detection and matching.

Copilot AI and others added 2 commits April 6, 2026 13:05
Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
Copilot AI changed the title [WIP] Add diagnostic check and dismissable modal for vulnerabilities feat(032): Security Advisories Check – spec, plan, tasks, and config Apr 6, 2026
Copilot AI requested a review from ildyria April 6, 2026 13:07
Copilot AI and others added 3 commits April 6, 2026 14:01
…point, tests

Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
…document singleton composable, fix controller trailing blank line

Agent-Logs-Url: https://github.com/LycheeOrg/Lychee/sessions/9946b46a-03f9-4ed0-bb55-8c1dcba27496

Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
@ildyria ildyria marked this pull request as ready for review April 6, 2026 16:25
@ildyria ildyria requested a review from a team as a code owner April 6, 2026 16:25
@ildyria
Copy link
Copy Markdown
Member

ildyria commented Apr 6, 2026

@coderabbitai review ?

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

@ildyria Sure, I'll review the PR now!


🧠 Learnings used
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T11:39:07.518Z
Learning: Applies to docs/specs/4-architecture/features/**/tasks.md : Maintain a per-feature tasks checklist at docs/specs/4-architecture/features/<NNN>-<feature-name>/tasks.md that mirrors the plan, orders tests before code, and keeps planned increments ≤90 minutes by preferring finer-grained entries and documenting sub-steps.

Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T11:39:07.518Z
Learning: Update specs before code. For every task, refresh the relevant feature plan and note open questions; only move forward once the plan reflects the desired change.

Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T11:39:07.518Z
Learning: Track upcoming additions for contract tests, mutation analysis, and security/red-team prompt suites in the plans until automated jobs exist (quality gates).

Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T11:39:07.518Z
Learning: Applies to docs/specs/4-architecture/features/**/*.md : Update feature specs, feature plans, and tasks documents as progress is made to maintain the single source of truth for each feature's requirements, design, and implementation status.

Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-16T11:39:07.518Z
Learning: Applies to docs/specs/4-architecture/features/**/plan.md : Generate or refresh the feature plan at docs/specs/4-architecture/features/<NNN>-<feature-name>/plan.md only after the specification is current and high-/medium-impact clarifications are resolved and recorded in the spec (plus ADRs where required).
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

A new security advisories feature is added that fetches GitHub Security Advisories, evaluates which vulnerabilities match the installed Lychee version, and surfaces matching advisories to administrators through a diagnostic pipeline, REST API endpoint, and login-time dismissible modal UI.

Changes

Cohort / File(s) Summary
Core Services & DTOs
app/Services/SecurityAdvisoriesService.php, app/Services/VersionRangeChecker.php, app/DTO/SecurityAdvisory.php
New service for fetching and filtering GitHub Security Advisories, version range constraint parser with operator support (>=, <=, >, <, =, !=), and immutable advisory DTO with CVE/GHSA IDs and CVSS metadata.
Diagnostic Integration
app/Actions/Diagnostics/Errors.php, app/Actions/Diagnostics/Pipes/Checks/SecurityAdvisoriesCheck.php
Diagnostic pipe check that evaluates advisories for admins when feature flag is enabled, formats CVSS scores, and appends error entries with advisory summaries.
HTTP Layer
app/Http/Controllers/Admin/SecurityAdvisoriesController.php, app/Http/Requests/Admin/SecurityAdvisories/IndexSecurityAdvisoriesRequest.php, app/Http/Resources/Models/SecurityAdvisoryResource.php
Admin endpoint controller gated by feature flag and admin authorization, form request with authorization logic, and resource for serializing advisories to API responses.
Configuration & Metadata
config/features.php, config/urls.php, app/Metadata/Cache/RouteCacheManager.php, app/Metadata/Json/ExternalRequestFunctions.php, app/Metadata/Json/AdvisoriesRequest.php
Feature flag for vulnerability checking, GitHub API endpoint and cache TTL configuration, non-cacheable route marking, and HTTP request setup with Accept header for GitHub API.
Frontend Components
resources/js/components/modals/SecurityAdvisoriesModal.vue, resources/js/composables/modals/useAdvisoryModal.ts, resources/js/services/security-advisories-service.ts
Dismissible modal component displaying advisories with GitHub advisory links, composable managing modal state with sessionStorage-based per-tab dismissal, and axios service for fetching advisories.
TypeScript Definitions
resources/js/lychee.d.ts
Type definition for SecurityAdvisoryResource with CVE/GHSA IDs, summary, and CVSS data.
Page Integration
resources/js/views/LoginPage.vue, resources/js/views/gallery-panels/Album.vue, resources/js/views/gallery-panels/Albums.vue, resources/js/views/gallery-panels/Tag.vue, resources/js/views/gallery-panels/Timeline.vue
Integration of advisoryCheck() into login and gallery refresh flows to trigger advisory evaluation for authenticated admins.
Routing
routes/api_v2.php
New admin-only GET endpoint /Security/Advisories returning matching advisories.
Translations
lang/*/dialogs.php (22 languages)
Security advisories modal UI text (title, description, no CVSS label, diagnostics navigation) across Arabic, Bulgarian, Czech, German, Greek, English, Spanish, Farsi, French, Hungarian, Italian, Japanese, Dutch, Norwegian, Polish, Portuguese, Russian, Slovak, Swedish, Turkish, Vietnamese, Simplified/Traditional Chinese.
Tests
tests/Feature_v2/SecurityAdvisories/IndexTest.php, tests/Unit/Services/SecurityAdvisoriesServiceTest.php, tests/Unit/Services/VersionRangeCheckerTest.php, tests/Unit/Actions/Diagnostics/SecurityAdvisoriesCheckTest.php, tests/Fixtures/github-security-advisories.json, tests/ImageProcessing/Import/ImportFromServerBrowseTest.php, tests/Unit/Caching/CachingConfigTest.php
Feature and unit tests covering authorization, feature flag gating, advisory filtering/deduplication/sorting, version range constraint evaluation, diagnostic formatting; GitHub API response fixture; caching test namespace migration.
Documentation
docs/specs/4-architecture/features/032-security-advisories/*.md, docs/specs/4-architecture/roadmap.md, docs/specs/4-architecture/open-questions.md
Architecture specification (feature plan, detailed spec, task checklist), resolved design questions (Q-032-01 through Q-032-10), and roadmap entry with 6 increments and 18 planned tasks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 From GitHub's vault, advisories hop,
We check the versions, stop-stop-stop!
Admins see warnings in dismissible cheer,
Version constraints parsed without fear! 🔒✨

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/specs/4-architecture/roadmap.md (1)

111-111: ⚠️ Potential issue | 🟡 Minor

Update the "Last updated" date.

The roadmap's "Last updated" timestamp (line 111) still shows 2026-03-18 but should be updated to reflect this change (e.g., 2026-04-06). As per coding guidelines, documentation files should have the update date refreshed when modified.

🧹 Nitpick comments (9)
lang/fa/dialogs.php (1)

297-302: Localize the new advisory dialog strings for Persian users.

Line 298 through Line 301 are currently English, which creates mixed-language UI in fa. Please provide Persian translations for these new keys.

lang/nl/dialogs.php (1)

296-301: Please translate the new advisory strings in Dutch locale.

At Line 297 through Line 300, new values are English in lang/nl, which will show mixed-language modal content.

lang/ru/dialogs.php (1)

296-301: Localize new advisory labels for Russian users.

Line 297 through Line 300 are currently English in lang/ru; translating these will avoid mixed-language UI in the new modal.

lang/zh_CN/dialogs.php (1)

299-304: Translate new advisory strings for zh_CN.

Line 300 through Line 303 are English; adding Simplified Chinese values here will keep modal UX consistent.

docs/specs/4-architecture/open-questions.md (1)

2719-2719: Update the "Last updated" date to reflect recent changes.

The resolved questions Q-032-01 through Q-032-10 were opened and resolved on 2026-04-06, but the "Last updated" line still shows 2026-03-15. As per coding guidelines, documentation files should update this date when modified.

-*Last updated: 2026-03-15*
+*Last updated: 2026-04-06*
resources/js/views/LoginPage.vue (1)

54-56: Replace async/await with .then() in this Vue method.

The goBack() method violates the project's Vue guideline by using async/await. Convert to promise chaining per coding guidelines for **/*.vue files.

♻️ Proposed fix
-async function goBack() {
-	await Promise.allSettled([lycheeStore.load(), userStore.refresh()]);
-	advisoryCheck();
-	router.push({ name: "gallery" });
-}
+function goBack() {
+	Promise.allSettled([lycheeStore.load(), userStore.refresh()]).then(() => {
+		advisoryCheck();
+		router.push({ name: "gallery" });
+	});
+}
resources/js/views/gallery-panels/Albums.vue (1)

273-278: Use Promise chaining instead of await in this Vue handler.

Line 273 introduces await in a Vue file; switch this handler to .then() to match repo conventions.

♻️ Suggested change
-async function onLoggedIn() {
-	await Promise.allSettled([lycheeStore.load(), userStore.refresh()]);
-	albumsStore.load(router);
-	orderManagementStore.refresh();
-	advisoryCheck();
-}
+function onLoggedIn() {
+	Promise.allSettled([lycheeStore.load(), userStore.refresh()]).then(() => {
+		albumsStore.load(router);
+		orderManagementStore.refresh();
+		advisoryCheck();
+	});
+}

As per coding guidelines: **/*.vue: “Do not use await async calls in Vue3, use .then() instead”.

resources/js/composables/modals/useAdvisoryModal.ts (1)

28-43: Make advisoryCheck() idempotent.

The singleton comment promises one check per admin login, but every call before dismissal still issues another request. Add an inFlight/hasChecked guard so repeated mounts or route re-entries do not keep hitting /Security/Advisories.

♻️ One way to guard duplicate checks
 const isAdvisoriesVisible = ref(false);
 const advisories = ref<App.Http.Resources.Models.SecurityAdvisoryResource[]>([]);
+let advisory_check_in_flight: Promise<void> | null = null;
+let has_checked_advisories = false;
@@
 	function advisoryCheck() {
-		if (sessionStorage.getItem(DISMISSED_KEY) !== null) {
+		if (
+			sessionStorage.getItem(DISMISSED_KEY) !== null ||
+			has_checked_advisories ||
+			advisory_check_in_flight !== null
+		) {
 			return;
 		}
 
-		SecurityAdvisoriesService.getAdvisories()
+		advisory_check_in_flight = SecurityAdvisoriesService.getAdvisories()
 			.then((response) => {
+				has_checked_advisories = true;
 				if (response.data.length > 0) {
 					advisories.value = response.data;
 					isAdvisoriesVisible.value = true;
 				}
 			})
 			.catch(() => {
+				has_checked_advisories = true;
 				// 401/403 for non-admins or network errors: silently ignore.
+			})
+			.finally(() => {
+				advisory_check_in_flight = null;
 			});
 	}
tests/Unit/Services/SecurityAdvisoriesServiceTest.php (1)

58-60: Guard fixture loading with an explicit assertion.

If the fixture path becomes invalid, this currently fails later during decode; adding an immediate assertion makes failures clearer.

Suggested tweak
 		// Decode fixture JSON.
 		$json = file_get_contents(base_path('tests/Fixtures/github-security-advisories.json'));
+		$this->assertNotFalse($json, 'Failed to read tests/Fixtures/github-security-advisories.json');
 		$this->fixture_data = json_decode($json, false, 512, JSON_THROW_ON_ERROR);

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af4f02e8-fa45-4166-8ca3-6571f7c7bb57

📥 Commits

Reviewing files that changed from the base of the PR and between 2c277b4 and 670f83a.

📒 Files selected for processing (58)
  • app/Actions/Diagnostics/Errors.php
  • app/Actions/Diagnostics/Pipes/Checks/SecurityAdvisoriesCheck.php
  • app/DTO/SecurityAdvisory.php
  • app/Http/Controllers/Admin/SecurityAdvisoriesController.php
  • app/Http/Requests/Admin/SecurityAdvisories/IndexSecurityAdvisoriesRequest.php
  • app/Http/Resources/Models/SecurityAdvisoryResource.php
  • app/Metadata/Cache/RouteCacheManager.php
  • app/Metadata/Json/AdvisoriesRequest.php
  • app/Metadata/Json/ExternalRequestFunctions.php
  • app/Services/SecurityAdvisoriesService.php
  • app/Services/VersionRangeChecker.php
  • config/features.php
  • config/urls.php
  • docs/specs/4-architecture/features/032-security-advisories/plan.md
  • docs/specs/4-architecture/features/032-security-advisories/spec.md
  • docs/specs/4-architecture/features/032-security-advisories/tasks.md
  • docs/specs/4-architecture/open-questions.md
  • docs/specs/4-architecture/roadmap.md
  • lang/ar/dialogs.php
  • lang/bg/dialogs.php
  • lang/cz/dialogs.php
  • lang/de/dialogs.php
  • lang/el/dialogs.php
  • lang/en/dialogs.php
  • lang/es/dialogs.php
  • lang/fa/dialogs.php
  • lang/fr/dialogs.php
  • lang/hu/dialogs.php
  • lang/it/dialogs.php
  • lang/ja/dialogs.php
  • lang/nl/dialogs.php
  • lang/no/dialogs.php
  • lang/pl/dialogs.php
  • lang/pt/dialogs.php
  • lang/ru/dialogs.php
  • lang/sk/dialogs.php
  • lang/sv/dialogs.php
  • lang/tr/dialogs.php
  • lang/vi/dialogs.php
  • lang/zh_CN/dialogs.php
  • lang/zh_TW/dialogs.php
  • resources/js/components/modals/SecurityAdvisoriesModal.vue
  • resources/js/composables/modals/useAdvisoryModal.ts
  • resources/js/lychee.d.ts
  • resources/js/services/security-advisories-service.ts
  • resources/js/views/LoginPage.vue
  • resources/js/views/gallery-panels/Album.vue
  • resources/js/views/gallery-panels/Albums.vue
  • resources/js/views/gallery-panels/Tag.vue
  • resources/js/views/gallery-panels/Timeline.vue
  • routes/api_v2.php
  • tests/Feature_v2/SecurityAdvisories/IndexTest.php
  • tests/Fixtures/github-security-advisories.json
  • tests/ImageProcessing/Import/ImportFromServerBrowseTest.php
  • tests/Unit/Actions/Diagnostics/SecurityAdvisoriesCheckTest.php
  • tests/Unit/Caching/CachingConfigTest.php
  • tests/Unit/Services/SecurityAdvisoriesServiceTest.php
  • tests/Unit/Services/VersionRangeCheckerTest.php

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 6, 2026

Codecov Report

❌ Patch coverage is 88.73239% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.16%. Comparing base (2c277b4) to head (670f83a).
⚠️ Report is 3 commits behind head on master.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ildyria ildyria merged commit 9167aef into master Apr 6, 2026
45 checks passed
@ildyria ildyria deleted the copilot/add-diagnostic-check-modal branch April 6, 2026 18:33
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.

2 participants