Skip to main content

Scheduled Scans

The Scheduled Scans page is your per-tenant cron table. One row → one celery-beat task → fresh findings on the cadence you pick. Works for every scan type Cybrium supports — Email Security, M365, SSL/TLS, DAST, VAPT, Secret Scan, NHI, VM, SBOM, VDI, AI Red-Team, and anything we add later.

Where to find it

Settings → Scheduled Scans (or https://app.cybrium.ai/settings?tab=schedules)

What a schedule does

When a schedule fires, the platform:

  1. Creates a fresh ScanJob with the schedule's scan_type, target, and scope
  2. Dispatches it through the standard scan engine — exactly the same code path as a manual scan
  3. Persists findings to your tenant's Findings table
  4. Updates the schedule's last_run_at, last_run_status, and last_scan_id so the dashboard reflects reality

There is no hidden side-loop — a scheduled scan is identical to a manual scan, just dispatched by cron.

Creating a schedule

Click New schedule. The drawer asks for:

FieldWhat it does
NameOperator-visible label, e.g. Daily Email Security for example.com
Description (optional)Why this schedule exists; who owns it
Scan typeAny of the 12+ supported ScanTypes — Email Security, M365, SSL, DAST, VAPT, Secret Scan, NHI, VM, SBOM, VDI, Medical, AI Red-Team
TargetDomain, IP, repo URL, credential alias — interpretation depends on scan type. The target hint below the input matches what the picked scan type expects.
CadenceDaily (02:00 UTC) · Weekly (Mon 03:00 UTC) · Monthly (1st @ 04:00 UTC) · Custom cron
Cron expression (custom only)Standard 5-field crontab — 0 */6 * * * = every 6 hours, */30 * * * 1-5 = every 30 min on weekdays
EnabledUncheck to create disabled; toggle later from the row's pause/play button

Save. The platform provisions a django-celery-beat.PeriodicTask immediately, and the next firing happens on the next matching tick of the cron.

Row actions

Each schedule row offers four icons:

IconAction
Pause / PlayToggle enabled. Disabling tears down the celery-beat row immediately; re-enabling provisions a fresh one.
Run nowFires the schedule synchronously. Same effect as the cron firing — fresh ScanJob, dispatched through the standard engine.
EditRe-opens the drawer for editing. Save re-syncs the celery-beat row to match.
DeleteRemoves the schedule + celery-beat row. Already-running scans complete; historical findings stay in your Findings page.

Status semantics

The Last run column tells you whether the schedule is producing what you expect:

  • Never run — schedule is new and hasn't fired yet
  • Running — a scan dispatched by this schedule is currently in flight
  • Success — the last run completed cleanly
  • Partial — some tools in the scan returned findings but at least one runner errored
  • Failed — dispatch itself failed; check the celery-scan logs

Click the row to open the schedule and inspect the linked last scan_id.

API

Every screen action has an API equivalent — the page is a thin wrapper:

GET /api/scans/schedules/ # list (filters: ?scan_type=, ?enabled=)
POST /api/scans/schedules/ # create
GET /api/scans/schedules/<id>/ # retrieve
PATCH /api/scans/schedules/<id>/ # partial update
DELETE /api/scans/schedules/<id>/ # delete + tear down
POST /api/scans/schedules/<id>/run/ # fire now
POST /api/scans/schedules/<id>/toggle/ # flip enabled flag

Sample create:

curl -X POST https://app.cybrium.ai/api/scans/schedules/ \
-H "Content-Type: application/json" \
-b "$AUTH_COOKIE" \
-d '{
"name": "Daily Email Security for example.com",
"scan_type": "email_security",
"target": "example.com",
"cadence": "daily",
"enabled": true,
"scope": { "reputation": true }
}'

Operator playbook

Common setups you can build in 2 minutes each:

  • Daily Email Security per tenant domain — Daily, scan_type email_security, target your apex domain
  • Weekly SSL/TLS audit of public-facing hosts — Weekly, scan_type ssl, target the hostname
  • Daily DAST on a staging URL after deploy — Daily 03:00, scan_type dast, target the staging URL
  • Monthly NHI reconciliation across cloud credentials — Monthly, scan_type nhi, target the credential alias
  • Hourly secret scan on a hot repo (custom cron) — Custom 0 * * * *, scan_type secret_scan, target the repo URL
  • Daily M365 Benchmark + Email Posture — Two schedules sharing the same target, scan types m365 and email_security

Migrating legacy schedules

Cybrium previously had three per-type schedulers — Cloud Monitoring, Secret Scan, and Continuous Network Scan. Sprint 101 ships a management command that backfills the Secret Scan and Continuous Network rows into the new universal scheduler:

# Dry run first — reports what would change
python manage.py backfill_scan_schedules --dry-run

# All tenants
python manage.py backfill_scan_schedules

# One tenant
python manage.py backfill_scan_schedules --tenant <schema_name>

The backfill is idempotent — re-running it is safe; each migrated row is tagged with scope.source_legacy_model + scope.source_legacy_id so it's never duplicated. After creating the new row, the legacy celery-beat task is disabled (not deleted) so scans never double-fire. The original legacy DB rows stay intact for audit and rollback.

Cloud Monitoring schedules are intentionally not backfilled yet — their dispatch is keyed on ScanJob.source_type='cloud' rather than scan_type, and the universal scheduler needs an additional field before it can carry them. That follow-up is tracked.

Limits

  • Default cadences fire at fixed UTC times (02:00 / Mon 03:00 / 1st 04:00); use Custom cron if you need a specific local-time window
  • One ScanJob per fire — schedules don't chain (the Adversary engine handles multi-phase orchestration separately)
  • Custom cron is parsed as a standard 5-field crontab; 6-field with seconds is not supported
  • The schedule's target is a string today — multi-target scheduling uses the targets JSON list field via the API (the UI exposes a single input for now)