mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-24 20:18:06 +09:00
New builtin job security-feed (daily) pulls the normalized advisory feed from serverkit.ai/api/security-feed.json (a cached proxy of the repo's published GitHub Security Advisories) and: - notifies admins (security.alert, critical) when the running panel version falls inside an advisory's affected range, once per advisory; - fires a one-time post-fix reminder after an upgrade crosses a fix boundary when the advisory declares a "## Post-upgrade actions" section (e.g. rotate JWT_SECRET_KEY after GHSA-rm3m-9mvw-68fh). Dedupe keys, the last-good feed copy, and the previously-seen version live in SystemSettings, so restarts never re-notify and outbound failures serve the cached feed. Opt out via the security_feed.enabled setting. Privacy page on serverkit.ai updated to disclose the daily call.
144 lines
5.0 KiB
Python
144 lines
5.0 KiB
Python
"""Tests for the security-advisory feed check (security_feed_service)."""
|
|
import pytest
|
|
|
|
from app import db as _db
|
|
from app.models.system_settings import SystemSettings
|
|
from app.services import security_feed_service as sfs
|
|
|
|
FEED = [
|
|
{
|
|
'ghsa': 'GHSA-test-0001',
|
|
'cve': None,
|
|
'severity': 'high',
|
|
'summary': 'Test advisory one',
|
|
'affected': '< 1.7.68',
|
|
'fixed_in': '1.7.68',
|
|
'url': 'https://example.test/GHSA-test-0001',
|
|
'published_at': '2026-08-01T00:00:00Z',
|
|
'post_fix_action': 'Rotate JWT_SECRET_KEY.',
|
|
},
|
|
{
|
|
'ghsa': 'GHSA-test-0002',
|
|
'cve': None,
|
|
'severity': 'medium',
|
|
'summary': 'Test advisory two',
|
|
'affected': '>= 1.5.0, < 1.6.0',
|
|
'fixed_in': '1.6.0',
|
|
'url': 'https://example.test/GHSA-test-0002',
|
|
'published_at': '2026-08-01T00:00:00Z',
|
|
'post_fix_action': None,
|
|
},
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def feed_env(app, monkeypatch):
|
|
"""Feed stub + notification recorder, inside an app context."""
|
|
sent = []
|
|
monkeypatch.setattr(sfs, 'fetch_feed', lambda: FEED)
|
|
from app.notifications.service import NotificationBusService
|
|
monkeypatch.setattr(
|
|
NotificationBusService, 'send',
|
|
classmethod(lambda cls, event, to, data=None, **kw: sent.append(
|
|
{'event': event, 'to': to, 'data': data, **kw})))
|
|
with app.app_context():
|
|
yield sent
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# version range evaluation
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
@pytest.mark.parametrize('version,range_str,expected', [
|
|
('1.7.59', '< 1.7.68', True),
|
|
('1.7.68', '< 1.7.68', False),
|
|
('1.7.67', '< 1.7.68', True),
|
|
('1.5.4', '>= 1.5.0, < 1.6.0', True),
|
|
('1.6.0', '>= 1.5.0, < 1.6.0', False),
|
|
('1.4.9', '>= 1.5.0, < 1.6.0', False),
|
|
('1.7.59', '', False),
|
|
('1.7.59', None, False),
|
|
('1.7.59', 'banana', False),
|
|
])
|
|
def test_version_in_range(version, range_str, expected):
|
|
assert sfs.version_in_range(version, range_str) is expected
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# affected-version alerting
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_alerts_when_current_version_affected(feed_env):
|
|
result = sfs.check_security_feed(current_version='1.7.59')
|
|
assert result['alerts'] == 1
|
|
assert len(feed_env) == 1
|
|
note = feed_env[0]
|
|
assert note['event'] == 'security.alert'
|
|
assert note['to'] == 'admins'
|
|
assert 'GHSA-test-0001' in note['data']['alert_type']
|
|
assert '1.7.59' in note['data']['message']
|
|
|
|
|
|
def test_no_alert_when_version_not_affected(feed_env):
|
|
result = sfs.check_security_feed(current_version='1.7.77')
|
|
assert result['alerts'] == 0
|
|
assert feed_env == []
|
|
|
|
|
|
def test_alert_fires_once_per_advisory(feed_env):
|
|
sfs.check_security_feed(current_version='1.7.59')
|
|
result = sfs.check_security_feed(current_version='1.7.59')
|
|
assert result['alerts'] == 0
|
|
assert len(feed_env) == 1
|
|
|
|
|
|
def test_multi_clause_range_alerts(feed_env):
|
|
result = sfs.check_security_feed(current_version='1.5.4')
|
|
assert result['alerts'] == 2 # both advisories match 1.5.4
|
|
|
|
|
|
def test_disabled_skips_everything(app, feed_env):
|
|
SystemSettings.set(sfs.ENABLED_KEY, False, 'boolean')
|
|
_db.session.commit()
|
|
result = sfs.check_security_feed(current_version='1.7.59')
|
|
assert result == {'skipped': 'disabled'}
|
|
assert feed_env == []
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# post-fix reminders after an upgrade crosses the fix boundary
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def test_post_fix_reminder_after_upgrade_crossing(feed_env):
|
|
# Panel was last seen on an affected version...
|
|
sfs.check_security_feed(current_version='1.7.59')
|
|
assert len(feed_env) == 1
|
|
# ...and comes up fixed: the advisory carries a post_fix_action.
|
|
result = sfs.check_security_feed(current_version='1.7.77')
|
|
assert result['alerts'] == 1
|
|
note = feed_env[-1]
|
|
assert 'Post-update action' in note['data']['alert_type']
|
|
assert 'Rotate JWT_SECRET_KEY' in note['data']['message']
|
|
|
|
|
|
def test_no_post_fix_reminder_without_action(feed_env):
|
|
# 1.5.4 -> 1.6.0 crosses GHSA-test-0002, which declares no post-fix action.
|
|
sfs.check_security_feed(current_version='1.5.4')
|
|
sent_before = len(feed_env)
|
|
sfs.check_security_feed(current_version='1.6.0')
|
|
assert len(feed_env) == sent_before
|
|
|
|
|
|
def test_post_fix_reminder_fires_once(feed_env):
|
|
sfs.check_security_feed(current_version='1.7.59')
|
|
sfs.check_security_feed(current_version='1.7.77')
|
|
result = sfs.check_security_feed(current_version='1.7.77')
|
|
assert result['alerts'] == 0
|
|
|
|
|
|
def test_first_seen_version_records_without_reminder(feed_env):
|
|
"""A fresh panel (no last_version) never gets post-fix reminders."""
|
|
result = sfs.check_security_feed(current_version='1.7.77')
|
|
assert result['alerts'] == 0
|
|
assert SystemSettings.get(sfs.LAST_VERSION_KEY) == '1.7.77'
|