Security advisory watch
Template: security-advisory-watch ยท category: security ยท risk: monitor_only
Poll an RSS/Atom security feed on an interval and page a channel on every new advisory whose title matches a keyword โ deduped on the advisory link, so an edited advisory never pages twice, and the first poll only records a baseline (booting on an established feed does not page its history). No networks, no signer: neither embedded engine boots.
This is the reference use of the trigger.web
HTTP polling trigger.
Honest scope
Web signal, not browser automation. rflow fetches and parses the feed
(with an rflow/<version> User-Agent, a 30s timeout and a 2 MiB body cap) โ
it never logs in, clicks, or drives a browser. Respect the feed operator's
robots.txt and rate limits: poll no faster than the feed actually updates.
When to use it
- page the on-call when a dependency posts a critical security advisory
- watch a protocol's announcements feed for pause/upgrade/incident notices
- turn any RSS/Atom feed into deduped, journaled, replayable rflow runs
How it works
# recipe: partial
trigger:
web:
url: https://example.com/security/feed.xml
every: 5m
mode: rss
where: "${{ 'critical' in (item.title | lower) }}"
idempotency_key: "${{ item.link }}"whereis evaluated per feed entry over theitemroot ({ id, title, link, published, summary }). Only matching entries fire.idempotency_keymakes the advisory's link the claim identity: an edited advisory (same link, touched title) creates no second run. Drop it and the dedupe key falls back to the item's content fingerprint โ then an edit counts as a new item.- The message template reads the fired entry directly:
"Security advisory: ${{ item.title }} ${{ item.link }}".
Generate it
rflow new --template security-advisory-watch
cd security-advisory-watch
cp .env.example .env # DATABASE_URL + pager credentials
docker compose up -d
rflow validate && rflow startNon-interactive:
rflow new --template security-advisory-watch --yes \
--answer feed_url=https://example.com/security/feed.xml \
--answer match_keyword=critical \
--answer pager=pagerdutyInputs
| key | type | default |
|---|---|---|
project_name | string | security-advisory-watch |
feed_url | string | https://example.com/security/feed.xml |
feed_mode | choice (rss | atom) | rss |
poll_every | duration | 5m (floor 30s) |
match_keyword | string | critical |
channel | string | pager |
pager | choice (pagerduty | telegram) | pagerduty |
Required env vars
DATABASE_URL, plus PAGERDUTY_ROUTING_KEY or TG_BOT_TOKEN +
TG_CHAT_ID depending on the pager choice.
Rehearse offline
rflow test security-advisory-watch --fixture fixtures/advisory-item.jsonThe fixture is one parsed feed entry ({ "item": {...} }), so the page text
renders without touching the live feed.
Common modifications
- watch a JSON API instead:
mode: jsonwithpath: "$.advisories" - watch a status page:
mode: html_textwithselector: ".incident"andchanged: true(fire on any change rather than per new item) - authenticated feeds: add
headers: { Authorization: "Bearer ${{ secrets.feed_token }}" }โ header values are never journaled or logged