Skip to content

Instantly share code, notes, and snippets.

@minrk
Created June 2, 2026 18:52
Show Gist options
  • Select an option

  • Save minrk/8b63685611793498fb38934aacdb8006 to your computer and use it in GitHub Desktop.

Select an option

Save minrk/8b63685611793498fb38934aacdb8006 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# (c) 2026 Min RK
# License: BSD-3-Clause
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "click",
# "rich",
# ]
# ///
"""
List security advisories for a github org
Usage:
./list-security-advisories [org]
Default org is jupyterhub because I wrote this.
"""
import json
from datetime import datetime, timedelta, UTC
import click
from rich.console import Console
from rich.table import Table, Column
from subprocess import check_output
def _fmt_age(age: timedelta) -> str:
"""format a timedelta as a simple age in days"""
if age < timedelta(days=7):
color = "[green]"
elif age > timedelta(days=30):
color = "[red]"
else:
color = ""
if age.days == 1:
s = "yesterday"
elif age.days:
s = f"{age.days} days ago"
else:
h = age.seconds // 3600
m = age.seconds // 60
s = f"{h:02}:{m:02} ago"
return f"{color}{s}"
def _sort_key(advisory) -> tuple[int, int, str]:
"""construct sort order for advisories
first by state, then by severity, then by date
"""
state_order = [
"triage",
"draft",
"published",
"closed",
]
severity_order = [
None,
"critical",
"high",
"medium",
"low",
]
state_key = state_order.index(advisory["state"])
severity_key = severity_order.index(advisory["severity"])
return (
state_key,
severity_key,
advisory["created_at"],
)
def _color_word(word: str) -> str:
"""
Pick a color for keyword highlighting
"""
color = ""
match word:
case "critical":
color = "[red]"
case "high":
color = "[magenta]"
case "triage":
color = "[green]"
return f"{color}{word}"
@click.command()
@click.argument("orgs", nargs=-1, required=True)
@click.option(
"--all",
default=False,
type=bool,
help="include all (default: just triage and draft)",
)
def main(orgs: str, all: bool = False):
# fetch advisories for a given org
# (assumes gh cli is authenticated)
advisories = []
for org in orgs:
advisory_json = check_output(
["gh", "api", f"/orgs/{org}/security-advisories"], text=True
)
org_advisories = json.loads(advisory_json)
for advisory in org_advisories:
advisory['org'] = org
advisories.extend(org_advisories)
# import pprint
# pprint.pprint(advisories[-1])
# return
advisories = sorted(advisories, key=_sort_key)
table = Table(
"State",
"Severity",
"Org",
"Title",
"Created",
"Updated",
Column("URL", no_wrap=True),
title=f"{len(advisories)} advisories",
)
now = datetime.now(UTC)
for advisory in advisories:
if all or advisory["state"] in {"triage", "draft"}:
updated = datetime.fromisoformat(advisory["updated_at"])
created = datetime.fromisoformat(advisory["created_at"])
age = _fmt_age(now - created)
updated_age = _fmt_age(now - updated)
table.add_row(
_color_word(advisory["state"]),
# severity can be None
_color_word(advisory["severity"] or ""),
advisory["org"],
advisory["summary"],
age,
updated_age,
advisory["html_url"],
)
console = Console()
console.print(table)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment