safedep query exec can drive an
alert. The recipe stays the same for every destination:
- Write the SQL for the events you care about.
- Run
safedep query exec -o jsonto get rows. - Format the rows into your destination’s payload shape.
POSTto the webhook.
curl target for your own destination.
Example: Slack alert for PMG blocks
Every 5 minutes, a scheduled job queries SafeDep Cloud for packages PMG blocked in the last 5 minutes and posts a Slack message like:Prerequisites
You always need:safedepCLI installed and signed in. See the SafeDep Cloud Quickstart.
python3andcurlon the host running the script.- A Slack Incoming Webhook URL. See Slack’s Sending messages using incoming webhooks.
Step 1: Write the query
Save the SQL to a file so you can version it in git and rerun it:blocks.sql
package_actionfilter.PMG_PACKAGE_ACTION_BLOCKEDis a malicious-package block.PMG_PACKAGE_ACTION_COOLDOWN_BLOCKEDis a cooldown-window block (packages held back until they age past your cooldown threshold).JOIN endpoints. Each block event was produced by a PMG invocation on some developer machine or CI runner. Joiningendpointsgives you the endpoint’s human-readable name (endpoints.identifier) and its ID (endpoints.id, used to build the Cloud deep-link). TheONclause is a required placeholder: SafeDep Cloud applies the real join from its catalog. See the SQL reference.
package_action and package_ecosystem) return numeric
ordinals in JSON; the formatter decodes them in Step 2. For the full schema,
tables, and enum values, see the SafeDep Cloud SQL reference.
Step 2: Format events for Slack
Slack’s Incoming Webhook takes a JSON payload with ablocks array. The
script below reads rows from stdin, decodes the enum ordinals, builds one
Slack section block per event, and prints the payload on stdout.
Save this as format.py:
format.py
ECOSYSTEM dict; nothing else needs to change.
Step 3: Post to Slack
Pipe the query into the formatter and the formatter intocurl:
format.py exits with empty stdout and
curl sends nothing. On a successful post, Slack replies with ok.
Slack caps a message at 50 blocks, so keep
--limit around 10. See Slack’s block limits.Step 4: Run it on a schedule
To make this an alerting pipeline, filter to a rolling window and run periodically. The window and the schedule interval must match so events aren’t dropped or duplicated. Save this assafedep-alerts.sh next to format.py. It’s the same query as
blocks.sql, plus one extra AND clause for the time window, an explicit
PATH (schedulers run with a minimal PATH), and a skip when the window
has no events (avoids Slack’s invalid_payload on empty POSTs).
safedep-alerts.sh
- macOS (launchd)
- Linux (cron)
On macOS, Load, verify, and tail the log:
cron can’t reach the keychain where safedep auth login
stores its OAuth token, so scheduled safedep query exec calls fail
with not authenticated. Use a launchd user agent instead: it runs
in your logged-in user session and inherits keychain access.Save this as ~/Library/LaunchAgents/io.safedep.alerts.plist:io.safedep.alerts.plist
RunAtLoad fires the job immediately. StartInterval is in seconds
(300 = 5 minutes) and must match the -v-5M window in the script.To stop it:--limit 10 is set for Slack’s 50-block ceiling. Events beyond the limit in
a single window are silently dropped. For burstier traffic, raise --limit
(the CLI allows up to 100) and page through the JSON next_page_token,
sending one Slack message per page.
Send to other destinations
Only the last two things change: the payload shape (informat.py) and the
URL (in curl). Any JSON-accepting HTTP endpoint works.
Discord
Emit a simple content payload and POST to your channel’s webhook URL.
Microsoft Teams
Create a channel Workflow with a webhook trigger and POST an Adaptive
Card payload to its URL.
PagerDuty
Emit an Events API v2 payload and POST to the enqueue endpoint.
Query other events
Swapblocks.sql for any question you can ask SafeDep Cloud:
- Insecure bypasses: filter
package_guard_eventsonevent_type = 'PMG_EVENT_TYPE_INSECURE_BYPASS'. - Endpoint activity:
JOIN endpointsand group byendpoints.identifierto see which machines produced the most events. - Malicious packages across projects: query
component_malicious_packageswithis_verified = true.

