Find old, privileged scheduled jobs before they become operational risk.
Undertaker is a read-only Python tool for finding scheduled jobs that may be old, privileged, or both.
It audits Linux cron jobs, Linux systemd timers, and Windows Scheduled Tasks, then prints a human-readable table and optionally writes JSON output for later review.
The name is thematic, but the tool stays plain: collect scheduled-task metadata, score age and privilege, and leave decisions to the operator.
- Read-only audit tool; it does not disable or delete tasks.
- Scans Linux cron, Linux systemd timers, and Windows Scheduled Tasks.
- Flags task definitions that are old, privileged, or both.
- Writes human-readable tables and structured JSON.
- Includes filters to reduce Windows baseline noise.
- Can emit JSON to stdout for pipelines.
- Can optionally check whether extracted command paths exist.
- Supports allowlists for known-good scheduled jobs.
- Includes Windows last-run metadata when available.
- Ships with tests, CI, issue templates, a security policy, and versioned releases.
Undertaker is built around three review questions:
What is still scheduled?How long has it been there?Does it run with more privilege than expected?
The output is plain by design. The goal is to make neglected automation visible enough for a human to close, allowlist, or investigate the right work.
flowchart LR
Sources["Scheduled job sources<br/>cron, systemd timers, Windows Scheduled Tasks"] --> Parser["Read-only collection<br/>names, commands, users, timestamps"]
Parser --> Signals["Triage signals<br/>age threshold, privilege, optional path checks"]
Signals --> Score["Severity<br/>none, medium, high"]
Score --> Output["Operator output<br/>table, JSON, summary"]
Output --> Decision["Human review<br/>keep, allowlist, investigate, remove outside the tool"]
More screenshots are available in docs/demo.md.
Scheduled automation is easy to forget. A task may be created for maintenance, startup behavior, updates, backups, or temporary troubleshooting, then quietly keep running for months or years.
That matters because scheduled jobs can run with elevated privileges such as root, SYSTEM, or Administrator. An old task with high privileges is not automatically malicious, but it deserves review.
This project helps answer:
What scheduled automation exists on this machine, how old is it, and does it run with elevated privileges?
On Linux:
/etc/crontab/etc/cron.d/*- User crontab spool locations
/etc/cron.hourly,/etc/cron.daily,/etc/cron.weekly, and/etc/cron.monthly- systemd timers discovered through
systemctl list-timers
On Windows:
- Scheduled Tasks through PowerShell
Get-ScheduledTask - Task actions, trigger types, principal user, run level, and task definition timestamps
The auditor flags a task when:
- The task definition is older than the configured threshold, defaulting to 180 days.
- The task appears to run with elevated privileges.
Severity:
none: no flagmedium: old or privilegedhigh: old and privileged
The flags are triage signals, not proof of compromise.
Install from a clone:
git clone https://github.com/srkyn/undertaker.git
cd undertaker
pip install .Run a scan:
uk # short alias
undertaker # full name
uk --only-suspicious # flagged tasks only
uk -d 90 # custom age threshold (short flag)
undertaker --days 90 # same, long formWrite JSON output:
uk -o results.json
uk --output results.jsonEmit JSON to stdout (for pipelines):
uk -f json -n
uk --format json --no-jsonCheck whether extracted command paths exist:
uk -p
uk --check-pathsWhen enabled, missing command paths are flagged as missing_command_path and shown in table output.
Suppress known-good tasks with an allowlist:
uk -a examples/allowlist.example.json
uk --allowlist examples/allowlist.example.jsonReduce Windows noise:
uk -w
uk --hide-windows-builtinCheck the installed version:
uk --versionThe scan ends with a summary:
Found X tasks, Y suspicious (Z high severity).
| Short | Long | Description |
|---|---|---|
-d N |
--days N |
Age threshold in days (default: 180) |
-o FILE |
--output FILE |
JSON output path |
-n |
--no-json |
Skip JSON file output |
-f FORMAT |
--format FORMAT |
Stdout format: table or json |
-s |
--only-suspicious |
Show flagged tasks only |
-w |
--hide-windows-builtin |
Hide built-in Microsoft tasks |
-p |
--check-paths |
Verify extracted command paths exist |
-a FILE |
--allowlist FILE |
Path to JSON allowlist |
JSON output includes each task's platform, type, name, command, run user, owner, schedule, source definition, modified timestamp, age in days, flags, risk reasons, and severity. Windows tasks include last-run metadata when available. When --check-paths is used, each task also includes command_path_exists.
Severity is simple:
medium: old or privilegedhigh: old and privileged
On a personal Windows test machine, the auditor found Windows Scheduled Tasks and separated them into review categories. Many built-in Windows tasks were flagged as medium because they run as SYSTEM or with the highest run level, which is expected behavior for operating system maintenance tasks.
The point is not to label every privileged task as bad. The point is to make privilege and age visible so a human can review what matters.
legacy_automation_auditor.py: the scannerdocs/design-notes.md: design notes, implementation details, and limitationsdocs/demo.md: screenshots and example outputexamples/allowlist.example.json: sample allowlistCHANGELOG.md: release history
- It does not modify, disable, or delete scheduled tasks.
- It does not prove whether a task is malicious.
- It may miss items the current user cannot read.
- It uses task definition modified time, not necessarily last run time.
- It does not deeply inspect scripts launched by tasks.
python -m py_compile legacy_automation_auditor.py
python -m unittest discover -s tests
uk --version
uk --no-json