SilverStripe security advisories should come from the official RSS feed
phptek opened this issue · 4 comments
At the moment, this package bakes-in YML files comprising official security advisories for SilverStripe (and a bunch of other applications). This seems a pretty high-maintenance procedure for a kind of data that is by its very nature, constantly evolving.
Is there a reason why the package doesn't make use of web-APIs and RSS feeds where available for each of the supported packages? The SilverStripe one is here: https://www.silverstripe.org/download/security-releases/rss
web-APIs and RSS feeds where available
Those feeds are designed for human consumption, not for machine consumption.
In a few lines of Python, I can parse that feed and compare with a project's composer.lock
.
#!/usr/bin/python
#
# Russell Michell 2018 <russellmichell@catalyst.net.nz>
#
# What is this?
#
# Parses the SilverStripe Security Releases RSS feed for vulnerabilities in the current project's
# composer.lock file.
#
# Requirements:
#
# Feedparser: pip install feedparser
# BeautifulSoup: apt-get install python-bs4
from bs4 import BeautifulSoup
import json, sys, feedparser, re
feed_url = 'https://www.silverstripe.org/download/security-releases/rss'
# We use this pseudo constant when no package is given in the RSS advisories
DEFAULT_PACKAGE = 'silverstripe/framework'
# Clean up version constraints
def clean_version(input):
return re.sub('[^[\d.]', '', input)
# Cleanup package-names
def clean_package(input):
return input.strip(' :')
with open('./composer.lock', 'r') as composerLockFile:
json = json.load(composerLockFile)
feed = feedparser.parse(feed_url)
if feed.channel == None:
print 'No feed data found.'
sys.exit(1)
for package in json['packages']:
c_package = package['name']
c_version = clean_version(package['version'])
for item in feed['items']:
soup = BeautifulSoup(item['description'], 'html.parser')
# All entity-encoded <dd> HTML elements found within an RSS <description> element
allDDs = soup.find_all('dd')
# TODO Can also use BS4 to access via class="foo" rather than as a dict/numeric-index
severity = allDDs[0].string
identifier = allDDs[1].string
versionsAffected = allDDs[2].string
versionsFixed = allDDs[3].string
advisory_link = item.link
# Advisories sometimes exclude the package name. Assume this means "silverstripe/framework"
# Advisories are formatted slightly differently (with/without colon-separated spaces between <package><version>)
package = re.sub("[\s:]?((>=?)?\s?)?(\d\.?)+(-rc\d)?,?", '', versionsFixed)
version = re.sub("([^\/]\w+\/[\w-]+(?=[\s:]))+", '', versionsFixed)
f_package = clean_package(package)
f_version = clean_version(version)
# Basic test to see if a package has been stipulated. If not, we assume: "silverstripe/framework"
if f_package is None or '/' not in f_package:
f_package = DEFAULT_PACKAGE
if c_package != f_package:
continue
for fixed_version in f_version.split(','):
is_vulnerable = c_version < fixed_version
if is_vulnerable:
print '[ALERT] %s version %s has a security advisory: %s' % (c_package, c_version, advisory_link)
# Break so no further versions need to be checked
break
sys.exit(1)
I'm going to close this one as we don't have any resources to maintain such a script and it is not our responsibility to create entries for all PHP projects out there. It's a contribution based effort from the community. Now, if the Silverstripe community wants to automate the process of submitting their advisories info, they can do so of course.