Port Cyber AlliancePort Cyber Alliance

Use the PCA IP blocklist and CVE feed

Get a key, pull the list in your firewall's format, update it automatically, and see what it holds today.

What the feeds hold, measured

Measured on 11 November 2026, 02:49 UTC, with one call per format on the live API, using a temporary key created for the test and revoked right after. We print the numbers as they are: today the connection works end to end and the content is only starting. Check the date above before you rely on a number.

2 entries in the IP blocklist
1 address and 1 range, both from the « declared » source.
No open source connected yet
The aggregator is built to merge open, redistributable blocklists. None is connected yet, so the list holds only the two examples.
Rebuilt once a day
Both feeds are rebuilt by a daily task. Last blocklist rebuild before the measurement: 10 November 2026, 15:51 UTC.

Six steps from key to automatic updates

For network engineers and SOC analysts of member organisations. Every command below is copy-paste; replace nothing but the key, and only in step 1.

1. Get an API key

Registration on the site is closed: keys are for member organisations, and accounts are opened by hand. Ask for an account through the contact form (the get-started page says we answer within one working day). Once your account is open, sign in, go to My account, then API Keys, and create a key, for yourself or for your organisation. The key is shown once: put it in your secret manager or in a file only root can read.

Where to ask: Get started with the PCA API (the account form is at the bottom) or the contact form. Once signed in: My account, API Keys.

sudo install -d -m 700 /etc/pca
sudo sh -c 'umask 077; cat > /etc/pca/api-key'
# paste the key (pca_live_xxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx), press Enter, then Ctrl-D:
# the key stays out of your shell history

2. Check the key

GET /whoami answers 200 with the key's scope and its daily quota. A wrong or revoked key gets a 401. The curl option -H @file reads the header from a file, so the key never shows in the process list.

(umask 077; printf 'X-API-Key: %s\n' "$(sudo cat /etc/pca/api-key)" > ~/.pca-header)
curl -sS -H @"$HOME/.pca-header" https://portcyberalliance.org/wp-json/pca/v1/whoami
# 200 → {"org_hash":…,"scope":"user","quota":{…}} · 401 → wrong or revoked key

3. Pull the list in the format your edge reads

One endpoint, GET /ip-blocklist, and a format parameter. These five formats answered 200 when we measured; any other value gets a 422.

B=https://portcyberalliance.org/wp-json/pca/v1/ip-blocklist
curl -sS -H @"$HOME/.pca-header" "$B?format=json"       # records with source, first_seen, last_seen
curl -sS -H @"$HOME/.pca-header" "$B?format=csv"        # ip,cidr,source,reason,last_seen
curl -sS -H @"$HOME/.pca-header" "$B?format=nginx"      # deny 203.0.113.7;
curl -sS -H @"$HOME/.pca-header" "$B?format=htaccess"   # Deny from 203.0.113.7
curl -sS -H @"$HOME/.pca-header" "$B?format=iptables"   # iptables -A INPUT -s 203.0.113.7 -j DROP

4. Apply it safely

Treat the answer as data, never as commands. Never pipe the iptables format into a shell: read the addresses, check each one, and load them into an ipset that a single firewall rule matches. For nginx, check every line, run nginx -t, and keep the previous file if the test fails. The pull script below does both, and applies nothing when the answer is not a 200 or holds an unexpected line. Note that an nginx deny written at http or server level is not inherited by a location that has its own allow or deny lines.

# nginx: once, in each server block to protect
include /etc/nginx/snippets/pca-blocklist.conf;

# ipset + iptables: once, after the first pull has created the two sets
iptables  -I INPUT -m set --match-set pca-blocklist    src -j DROP
ip6tables -I INPUT -m set --match-set pca-blocklist-v6 src -j DROP

5. Keep it up to date

The list is rebuilt once a day, so pulling every six hours is plenty and stays far below the daily quota. Install the script, then choose a systemd timer or a cron line.

The script, the two systemd units and the cron line are in the files folder and in full in the files section below.

sudo install -m 750 pca-blocklist-pull.sh /usr/local/sbin/
sudo install -m 644 pca-blocklist.service pca-blocklist.timer /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now pca-blocklist.timer
systemctl list-timers pca-blocklist.timer      # next run
journalctl -u pca-blocklist.service -n 20      # what the last run did
# or, without systemd: sudo install -m 644 pca-blocklist.cron /etc/cron.d/pca-blocklist

6. Read the CVE feed

GET /cve-feed lists the vulnerabilities that concern port software, as JSON or RSS, optionally for one product. Both formats answered 200 when we measured, with an empty list (see « What the feed covers »).

C=https://portcyberalliance.org/wp-json/pca/v1/cve-feed
curl -sS -H @"$HOME/.pca-header" "$C?format=json"                 # {"cves":[…]}
curl -sS -H @"$HOME/.pca-header" "$C?format=rss"                  # RSS 2.0 for a feed reader
curl -sS -H @"$HOME/.pca-header" "$C?format=json&product=<product>"

What each format gives

The same entries in five shapes. Measured answer of the nginx format on 11 November 2026, 02:49 UTC:

deny 203.0.113.7;
deny 198.51.100.0/24;

format=json
HTTP 200 · application/json · an object « blocklist » with one record per entry: ip, cidr, source, reason, first_seen, last_seen, active.
format=csv
HTTP 200 · text/csv · a header line « ip,cidr,source,reason,last_seen », then one quoted line per entry.
format=nginx
HTTP 200 · text/plain · one « deny <address or range>; » line per entry.
format=htaccess
HTTP 200 · text/plain · one « Deny from <address or range> » line per entry (Apache 2.2 syntax).
format=iptables
HTTP 200 · text/plain · one « iptables -A INPUT -s <address or range> -j DROP » line per entry.

The files, in full

Click a file to read it. They are also in the files folder, with a README and the licence (CC BY 4.0).

Pulls the list, checks every entry, applies it to nginx or to two ipsets, and applies nothing on a non-200 answer or an unexpected line. Install in /usr/local/sbin/.

#!/usr/bin/env bash
# pca-blocklist-pull.sh · Port Cyber Alliance · CC BY 4.0 · https://portcyberalliance.org/api-developers/ip-blocklist-cve-feed-howto/
# Pull the PCA IP blocklist, check every entry, apply it only if the whole answer is valid; otherwise keep the last good list.
#   pca-blocklist-pull.sh nginx   → writes the nginx snippet, runs nginx -t, reloads nginx
#   pca-blocklist-pull.sh ipset   → refills the ipsets pca-blocklist (IPv4) and pca-blocklist-v6 (IPv6)
# The key is read from a root-only file and sent with curl -H @file, so it never shows in the process list.
set -euo pipefail
MODE="${1:-nginx}"
URL="${PCA_URL:-https://portcyberalliance.org/wp-json/pca/v1/ip-blocklist}"
KEY_FILE="${PCA_API_KEY_FILE:-/etc/pca/api-key}"
STATE="${PCA_STATE_DIR:-/var/lib/pca-blocklist}"
SNIPPET="${PCA_NGINX_SNIPPET:-/etc/nginx/snippets/pca-blocklist.conf}"
SET="${PCA_IPSET:-pca-blocklist}"
TOKEN='^[0-9A-Fa-f:.]+(/[0-9]{1,3})?$'

umask 077; mkdir -p "$STATE"
body="$(mktemp "$STATE/body.XXXXXX")"; list="$(mktemp "$STATE/list.XXXXXX")"; hdr="$(mktemp "$STATE/hdr.XXXXXX")"
trap 'rm -f "$body" "$list" "$hdr"' EXIT
printf 'X-API-Key: %s\n' "$(tr -d '\r\n' < "$KEY_FILE")" > "$hdr"

case "$MODE" in nginx) fmt=nginx ;; ipset) fmt=csv ;; *) echo "usage: pca-blocklist-pull.sh nginx|ipset" >&2; exit 2 ;; esac
code="$(curl -sS --max-time 60 -H @"$hdr" -o "$body" -w '%{http_code}' "$URL?format=$fmt" || echo 000)"
[ "$code" = 200 ] || { echo "pca-blocklist: HTTP $code, nothing applied, last good list kept" >&2; exit 1; }

if [ "$MODE" = nginx ]; then
  sed -n 's/^deny \(.*\);$/\1/p' "$body" > "$list"
  if grep -Ev '^(deny [0-9A-Fa-f:.]+(/[0-9]{1,3})?;)?$' "$body" | grep -q .; then
    echo "pca-blocklist: unexpected line in the answer, nothing applied" >&2; exit 1
  fi
  mkdir -p "$(dirname "$SNIPPET")"
  [ -f "$SNIPPET" ] && cp -p "$SNIPPET" "$STATE/last-good.conf"
  { echo "# PCA IP blocklist · pulled $(date -u +%FT%TZ) · do not edit, rewritten by pca-blocklist-pull.sh"; cat "$body"; } > "$SNIPPET.new"
  chmod 644 "$SNIPPET.new"; mv "$SNIPPET.new" "$SNIPPET"
  if nginx -t -q; then
    systemctl reload nginx
  else
    if [ -f "$STATE/last-good.conf" ]; then cp -p "$STATE/last-good.conf" "$SNIPPET"; else : > "$SNIPPET"; fi
    echo "pca-blocklist: nginx -t failed, previous list restored" >&2; exit 1
  fi
else
  # CSV columns: ip,cidr,source,reason,last_seen · the entry is the range when there is one, else the address
  tail -n +2 "$body" | tr -d '"' | while IFS=, read -r ip cidr _; do
    if [ -n "$cidr" ]; then echo "$cidr"; elif [ -n "$ip" ]; then echo "$ip"; fi
  done > "$list"
  if grep -Ev "$TOKEN" "$list" | grep -q .; then
    echo "pca-blocklist: unexpected entry in the answer, nothing applied" >&2; exit 1
  fi
  ipset -exist create "$SET" hash:net family inet
  ipset -exist create "$SET-v6" hash:net family inet6
  ipset -exist create "$SET-new" hash:net family inet;     ipset flush "$SET-new"
  ipset -exist create "$SET-v6-new" hash:net family inet6; ipset flush "$SET-v6-new"
  while read -r t; do
    case "$t" in *:*) ipset -exist add "$SET-v6-new" "$t" ;; *) ipset -exist add "$SET-new" "$t" ;; esac
  done < "$list"
  ipset swap "$SET-new" "$SET";       ipset destroy "$SET-new"
  ipset swap "$SET-v6-new" "$SET-v6"; ipset destroy "$SET-v6-new"
fi
echo "pca-blocklist: $(grep -c . "$list" || true) entries applied ($MODE)"

The systemd service that runs the script once. Install in /etc/systemd/system/.

# Port Cyber Alliance · CC BY 4.0 · install in /etc/systemd/system/
[Unit]
Description=Pull the Port Cyber Alliance IP blocklist
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
Environment=PCA_API_KEY_FILE=/etc/pca/api-key
# nginx snippet; use « ipset » instead of « nginx » for the ipset + iptables setup
ExecStart=/usr/local/sbin/pca-blocklist-pull.sh nginx

Every six hours, spread by up to 15 minutes, and a missed run is caught up after downtime. Install in /etc/systemd/system/.

# Port Cyber Alliance · CC BY 4.0 · install in /etc/systemd/system/, then: systemctl enable --now pca-blocklist.timer
[Unit]
Description=Pull the Port Cyber Alliance IP blocklist every six hours

[Timer]
OnCalendar=*-*-* 00/6:00:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target

The same schedule for hosts without systemd. Install as /etc/cron.d/pca-blocklist.

# Port Cyber Alliance · CC BY 4.0 · install as /etc/cron.d/pca-blocklist (cron.d lines carry the user)
# every six hours, at a minute of your choice; the result goes to syslog under the tag pca-blocklist
17 */6 * * * root PCA_API_KEY_FILE=/etc/pca/api-key /usr/local/sbin/pca-blocklist-pull.sh nginx 2>&1 | logger -t pca-blocklist

What the feed covers

GET /cve-feed answers in JSON or RSS. When we measured, both formats answered 200 with an empty list:

{"cves":[]}

Where it comes from
Records of the US National Vulnerability Database (NVD), read through its public API.
What gets in
A record is served when one of its affected products matches a watchlist of port and industrial software (control systems, industrial networks, terminal software) and of the web stack the alliance runs itself.
What each entry holds
The CVE id, the CVSS base score, the affected products, an English summary and the publication date.
How often
One collection a day is scheduled. One had run when we measured, on 10 November 2026 at 15:51 UTC.
Today
The first collection read 200 records from the US National Vulnerability Database (NVD), all published between 1988 and 1997. None of them concerns the watched software, so the feed answered an empty list. Reading the recent records is the next step on our side.

IP blocklist and CVE feed: questions

Click a question to read the answer.

Keys are for member organisations and accounts are opened by hand: registration on the site is closed. Ask for an account through the form on the Get started page or the contact form. Once your account is open, sign in and create the key under My account, API Keys. It is shown once, so store it in a secret manager or a root-only file.

Five, all measured with a 200 on 11 November 2026: json, csv, nginx, htaccess and iptables. Any other value of the format parameter gets a 422 that lists the accepted values. A call without a key, or with a revoked key, gets a 401.

Small. On 11 November 2026, 02:49 UTC it held 2 entries: one address and one range, both in blocks that RFC 5737 reserves for documentation, so blocking them changes nothing on your network. No open blocklist source is connected to the aggregator yet. The measurement is dated on this page: check the date before you rely on it.

The list is rebuilt once a day, so every six hours is plenty. The test key's quota was 1,000 GET calls a day, so a pull every six hours uses four of them.

No. Treat the answer as data, never as commands: read the addresses, check that each one is only an address or a range, and load them into an ipset matched by one firewall rule. The pull script on this page does that, and applies nothing when the answer is not a 200 or holds an unexpected line.

Vulnerabilities from the US National Vulnerability Database whose affected products match a watchlist of port and industrial software and of the alliance's own web stack. On 11 November 2026, 02:49 UTC it answered an empty list: the first collection had read 200 records, all published between 1988 and 1997, and none matched. One collection a day is scheduled.

Yes. They are published under the Creative Commons Attribution 4.0 licence (CC BY 4.0): copy and adapt them, including commercially, as long as you credit Port Cyber Alliance and link to this page. Test them on a non-production host first, and keep an allowlist for your partners.

Ready to pull the list?

Accounts are opened by hand for member organisations. Tell us who you are and what you want to connect.

Need an API key?

Send this page to a colleague

Someone in your team should read this? Add their e-mail, we send them the page from you.

Your colleagues' addresses are used for this mail only and are not kept.

Contact the alliance

Use our contact form: every message is read by the team.

Report an incident

Port operators can report a cyber incident to the alliance at any time.