Skip to content

Added GHSA security advisory feed for Ghost vulnerabilities#27884

Draft
rob-ghost wants to merge 2 commits into
feat/operator-update-checksfrom
feat/ber-3651-ghsa-feed
Draft

Added GHSA security advisory feed for Ghost vulnerabilities#27884
rob-ghost wants to merge 2 commits into
feat/operator-update-checksfrom
feat/ber-3651-ghsa-feed

Conversation

@rob-ghost
Copy link
Copy Markdown
Contributor

Problem

Ghost operators have no automated way to learn about security advisories affecting their installed version. Critical vulnerabilities can sit undeployed for days or weeks because admins only find out through external channels (mailing lists, GitHub watches, security bulletins).

Solution

Add a daily background job that polls GitHub's repository security advisories API for the Ghost package. Advisories matching the running version surface as in-admin banners; critical-severity advisories also trigger a templated email to all admin users.

Gated by the same updateCheck.enabled flag as the existing update checker, so disabling telemetry disables advisory polling too. The feed lives inside the notifications domain as a downstream dependency, and a small runWorker helper handles the Bree worker bootstrap so future feeds can collapse to a one-line worker entry.

Test plan

  • Integration tests cover critical / high / out-of-version / draft-state / disabled / enabled paths
  • Manual: run fetchAdvisories() against the live GitHub endpoint and confirm schema parses
  • Manual: verify scheduled cron registers with the expected name and at-spec when enabled, no-ops when disabled

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6cabbeb9-1e4c-43f9-bbd7-eeb19bf5ba99

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ber-3651-ghsa-feed

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
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/server/services/notifications/feeds/run-worker.ts`:
- Around line 14-27: The current worker listens for parentPort.once('message')
and posts 'cancelled' on a 'cancel' message, but the async bootstrap still runs
permissions.init(), settings.init(), and fn() and then posts 'done', causing
both 'cancelled' and 'done' to be emitted and work to continue after
cancellation; fix by introducing a cancellation flag (e.g. let cancelled =
false) set to true inside the message handler for message === 'cancel' and check
that flag before running the init/ fn sequence (or abort early) and before
postMessage('done') so that if cancelled is true you skip calling fn() and do
not post 'done' (ensure the listener remains parentPort.once('message') and the
checks occur immediately before permissions.init()/fn() and before posting
'done').
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c57d7c69-3f21-4627-991a-ef0826918099

📥 Commits

Reviewing files that changed from the base of the PR and between 63dad01 and eddff05.

📒 Files selected for processing (12)
  • ghost/core/core/boot.js
  • ghost/core/core/server/services/mail/templates/critical-update.html
  • ghost/core/core/server/services/notifications/alert-email-reactor.ts
  • ghost/core/core/server/services/notifications/feeds/ghsa/index.ts
  • ghost/core/core/server/services/notifications/feeds/ghsa/worker.ts
  • ghost/core/core/server/services/notifications/feeds/run-worker.ts
  • ghost/core/core/server/services/notifications/index.js
  • ghost/core/core/server/services/notifications/index.ts
  • ghost/core/core/server/services/notifications/notification.ts
  • ghost/core/core/server/services/notifications/service.ts
  • ghost/core/core/shared/config/defaults.json
  • ghost/core/test/integration/services/notifications/notification.test.ts
💤 Files with no reviewable changes (1)
  • ghost/core/core/server/services/notifications/index.js

Comment on lines +14 to +27
parentPort.once('message', (message) => {
if (message === 'cancel') {
postMessage('cancelled');
}
});
}

(async () => {
try {
await permissions.init();
await settings.init();
await fn();
postMessage('done');
} finally {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent post-cancel execution from reporting success.

Line 14-18 acknowledges 'cancel', but Line 23-26 still executes fn() and posts 'done'. This can produce contradictory worker states (cancelled + done) and still run work after cancellation.

Proposed fix
 export function runWorker(fn: () => Promise<unknown>): void {
+    let cancelled = false;
+
     if (parentPort) {
         parentPort.once('message', (message) => {
             if (message === 'cancel') {
+                cancelled = true;
                 postMessage('cancelled');
             }
         });
     }
 
     (async () => {
         try {
             await permissions.init();
             await settings.init();
+            if (cancelled) {
+                return;
+            }
             await fn();
-            postMessage('done');
+            if (!cancelled) {
+                postMessage('done');
+            }
         } finally {
             if (!parentPort) {
                 setTimeout(() => process.exit(0), 1000);
             }
         }
     })();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/core/server/services/notifications/feeds/run-worker.ts` around
lines 14 - 27, The current worker listens for parentPort.once('message') and
posts 'cancelled' on a 'cancel' message, but the async bootstrap still runs
permissions.init(), settings.init(), and fn() and then posts 'done', causing
both 'cancelled' and 'done' to be emitted and work to continue after
cancellation; fix by introducing a cancellation flag (e.g. let cancelled =
false) set to true inside the message handler for message === 'cancel' and check
that flag before running the init/ fn sequence (or abort early) and before
postMessage('done') so that if cancelled is true you skip calling fn() and do
not post 'done' (ensure the listener remains parentPort.once('message') and the
checks occur immediately before permissions.init()/fn() and before posting
'done').

@rob-ghost rob-ghost force-pushed the feat/operator-update-checks branch from 63dad01 to c113035 Compare May 14, 2026 09:14
rob-ghost added 2 commits May 14, 2026 10:42
Notifications carrying a template name now render via the mail content
generator with their variables, producing both HTML and plaintext, while
plain-message notifications keep the existing behaviour. This enables
upcoming critical-update alerts to use a proper email template instead
of inlining HTML in the notification message.
ref https://linear.app/tryghost/issue/BER-3651

Polls the GitHub Security Advisories API daily and surfaces published
high/critical advisories that affect the running Ghost version as admin
notifications. Critical advisories also send a templated alert email to
administrators, while high-severity advisories show a dismissible banner
only. The check honours updateCheck:enabled and is gated on a configurable
advisoriesUrl so it can be disabled or pointed at a different source.
@rob-ghost rob-ghost force-pushed the feat/ber-3651-ghsa-feed branch from eddff05 to 02096a1 Compare May 14, 2026 09:42
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.

1 participant