A CVE is an identifier for a specific security vulnerability in a specific product. You might have heard of such CVEs as CVE-2014-0160, CVE-2017-5754 and CVE-2017-13077 through 13088, also known as Heartbleed, Meltdown and KRACK.

Technically the full name is a Common Vulnerabilities & Exposures Identifier, but that’s a mouthful.

At work we deal with a ton of these CVEs on a daily basis. There are a few online databases where you can go to look up details. Everyone has their own pet script for making this lookup fluent.

Here’s my latest, for Emacs on macOS:

(defun buttonize-buffer-with-cves (bufname)
  "Mark CVEs in a given buffer as hyperlinks."
  (interactive "bBuffer to add CVE buttons to: ")
  (save-excursion
    (with-current-buffer bufname
      (goto-char (point-min))
      (while (re-search-forward "CVE-[[:digit:]]\\{4\\}-[[:digit:]]\\{4,\\}" nil t)
        (let* ((start (match-beginning 0))
               (end (match-end 0))
               (cve (buffer-substring start end))
               (lexical-binding t))
          (make-button (match-beginning 0) (match-end 0)
                       'url (format "https://nvd.nist.gov/vuln/detail/%s" cve)
                       'action (lambda (button)
                                 (let ((url (button-get button 'url)))
                                   (shell-command (format "open '%s'" url)))
                                 'help-echo (format "Visit %s at NVD" cve))))))))

With this, you can go from:

Screenshot of before.

To:

Screenshot of after.

Now all the CVEs in the document are highlighted and clickable! Note that I’ve moused over CVE-2018-18498 and can middle-click it (or select it with the keyboard and press Enter) to open its corresponding CVE page. Yay!

Interesting bits of the code

(interactive "bBuffer to add CVE buttons to: ")

interactive is a cool macro and this lets you invoke the function interactively, with buffer names automatically suggested/completed by Emacs.

(while (re-search-forward ...))

Nice, easy way to iterate over matching text. You can use match-beginning and match-end to get the range of the match.

(re-search forward "CVE-[[:digit:]]\\{4\\}-[[:digit:]]\\{4,\\}" nil t)

All the Emacs Lisp docs cite \{ and \} for repetition, but because of escaping, what you actually use is \\{. Fun times. Thanks to M-x regexp-builder for making this (relatively) quick to diagnose, once I’d given up reading the docs and started experimenting.

(make-button ... 
             'url (format ...))
             'action (lambda (button)
                             (let ((url (button-get button 'url)) ...) ...))

You can set arbitrary properties like 'url on the button and retrieve them later with (button-get button 'url). Also, I think this is safe, but no guarantees. The argument to open is single-quoted so you’d need a single quote to break out of it, but the pattern being matched doesn’t permit single quotes. Not gonna lie, getting this working was enough trouble without going into shell escaping.

How do you make this useful?

Short term: You can M-x buttonize-buffer-with-cves.

Long term: It could be built into a mode you’re already creating…

No, but how do you make it useful enough to be worth the time investment?

Goodbye!