Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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 }}"
  • where is evaluated per feed entry over the item root ({ id, title, link, published, summary }). Only matching entries fire.
  • idempotency_key makes 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 start

Non-interactive:

rflow new --template security-advisory-watch --yes \
  --answer feed_url=https://example.com/security/feed.xml \
  --answer match_keyword=critical \
  --answer pager=pagerduty

Inputs

keytypedefault
project_namestringsecurity-advisory-watch
feed_urlstringhttps://example.com/security/feed.xml
feed_modechoice (rss | atom)rss
poll_everyduration5m (floor 30s)
match_keywordstringcritical
channelstringpager
pagerchoice (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.json

The 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: json with path: "$.advisories"
  • watch a status page: mode: html_text with selector: ".incident" and changed: 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