Scheduled jobs are where passwords, API keys and database connection strings go to be forgotten: written once into a crontab line, a wrapper script or a task definition, then copied to every new server. Anyone with administrative access to the host, and often any local user, can read them, and they are rarely rotated because nobody remembers they are there. This runbook shows how to find credentials in Linux cron jobs, systemd timers and their unit files, and Windows Task Scheduler tasks, including tasks that store an account password and legacy Group Policy Preferences tasks. It then covers how to move each credential to a safer mechanism and roll back if a job fails. It is for platform, Windows and Linux administrators and security teams running an NHI clean-up. For the attack side of scheduled jobs, see the T1053 Scheduled Task/Job Defensive Guide.
This is an independent guide from NHI Mgmt Group, based on the cronie crontab(5) manual, the systemd.exec(5) manual and Microsoft's Task Scheduler and security bulletin documentation as of November 2026. Paths and defaults vary by distribution and Windows version, so confirm on your own hosts.
Key takeaways
- Look in four layers, not one. Credentials hide in the schedule entry itself, in environment lines, in the scripts the job calls, and in files those scripts read. Searching only crontab lines misses most of them.
- Environment variables are not a secret store. The systemd manual states that environment variables are not suitable for passing secrets: unit variables are exposed to unprivileged clients over D-Bus and propagate to child processes.
- A Windows task with LogonType Password holds a stored password. S4U tasks store none, but cannot reach the network or encrypted files. Find Password tasks first.
- Old Group Policy Preferences tasks still leak passwords. MS14-025 stopped new GPP passwords from being set but did not remove existing GPOs; their passwords can be retrieved and decrypted by any authenticated user who can read them.
- Scan read-only, redact, then fix by priority. Use a secret scanner with redaction on exported job definitions, rank findings by what the credential unlocks, rotate it, and only then change the job.
When you need this
- NHI inventory and audit: NIST SP 800-53 IA-5(7) asks that unencrypted static authenticators are not embedded in applications or other forms of static storage. Scheduled jobs are one of the most common places they are.
- After a host compromise: ATT&CK T1552.001 Credentials In Files describes adversaries searching local files for insecurely stored credentials, and scheduled job scripts are an easy target. In the Uber Breach 2022, an intruder found a script on a network share containing hard-coded administrator credentials for a privileged access system, a reminder that automation scripts carry high-value secrets.
- Before decommissioning or migrating servers: jobs moved to containers or a scheduler service carry their embedded secrets with them unless someone looks.
- After an administrator leaves: tasks registered with a person's password break at the next password change, or keep running as that person if the account is not disabled.
Before you start
Map where jobs are defined on each platform. Paths in the Linux rows follow the cronie manual and common distribution layouts; check your own build.
| Platform | Where jobs are defined | Where credentials usually sit |
|---|---|---|
| cron (cronie, Vixie cron) | /etc/crontab, /etc/cron.d/, user crontabs under /var/spool/cron/ (Debian family: /var/spool/cron/crontabs/), scripts in /etc/cron.hourly, .daily, .weekly, .monthly | Environment lines (name = value), inline command arguments (mysqldump -p..., curl -u, URLs with tokens), called scripts and their config files |
| systemd timers | .timer units and the .service units they start, in /etc/systemd/system/, /usr/lib/systemd/system/ and user unit directories | Environment=, EnvironmentFile=, ExecStart= arguments, SetCredential= literals |
| at | The at spool directory (distribution specific) | Captured environment and commands of one-off jobs |
| Windows Task Scheduler | Task definitions viewable with schtasks /query /xml or Task Scheduler | Stored account passwords (LogonType Password), arguments to scripts, the scripts themselves |
| Group Policy Preferences | Scheduled and Immediate Task preference items in GPOs (SYSVOL) | Encrypted, recoverable account passwords in the preference XML |
| Kubernetes CronJob and CI schedules | CronJob manifests, scheduled pipeline definitions | Literal env values in pod specs, pipeline variables; see the linked guides below |
Run discovery with a read-only collection identity. Do not print findings to shared terminals or tickets in clear text: the output of this runbook is itself a list of live secrets. Agree a secure location for the report and a rotation owner for each system before you scan.
Step-by-step
Steps 1 to 5 are read-only. Steps 6 and 7 change jobs and credentials and need change control.
- Export every cron job with its source file. Collect
/etc/crontab, every file in/etc/cron.d/, every user crontab in the spool directory and the scripts in the periodic directories. If you run osquery, itscrontabtable returns parsed entries with thecommandand thepathof the file each row came from. Two cronie behaviours matter when reading the output: environment assignments appear as their own lines and apply to the jobs after them, and an unescaped%in a command becomes a newline, with the rest of the line passed to the command on standard input. A credential can therefore sit after a%, not in the visible command. - Export systemd timers and their services. List timers, then dump the service each one triggers, including drop-in files:
Flagsystemctl list-timers --all systemctl cat <name>.serviceEnvironment=lines with secret-like names, everyEnvironmentFile=path (and that file's owner and mode), secrets inExecStart=arguments, and anySetCredential=, whose literal value the systemd manual says is accessible to unprivileged processes over IPC. - Follow each job to the code it runs. Resolve the command of every job to a file: wrapper scripts, scripts they source, and configuration files they read (
.my.cnf,.pgpass,.netrc, cloud CLI credential files, application.envfiles). Record the file owner and mode; a world-readable script with a password is a finding even before you know what the password opens. The Environment Variables and .env Files guide covers that file type. - Export Windows tasks and find stored passwords.
schtasks /query /xmloutputs all task definitions on the system in XML, andschtasks /query /fo CSV /vgives a verbose table for a whole host (use/s <computer>for a remote host; the account used must be a local administrator there). In each definition, read the principal'sLogonType:Also record the principal: a task running as a domain account with a stored password is a service account credential sitting on every host the task was deployed to. Then read the action's command and arguments, and follow them to scripts as in step 3. Remember that administrators and SYSTEM can read any task, per Microsoft's security contexts documentation.LogonType Meaning (Microsoft task schema) Credential exposure PasswordThe user must log on with a password The account password is stored on the host for the task; highest priority InteractiveTokenOrPasswordNo longer in use, now identical to Password Treat as Password S4UService for user logon; no password is stored None stored, but no access to the network or encrypted files InteractiveTokenRuns only in an existing interactive session None stored - Check Group Policy for preference task passwords. Microsoft's MS14-025 bulletin explains that passwords configured through Group Policy Preferences for scheduled tasks, immediate tasks, services, local users, mapped drives and data sources can be retrieved and decrypted by an authenticated attacker, and that the update removed the ability to set them but did not remove existing GPOs. Search the preference XML files in SYSVOL for password attributes (ATT&CK T1552.006 Group Policy Preferences describes the
cpasswordattribute) and list every GPO that still carries one. - Scan the exported material with a secret scanner. Copy job definitions and resolved scripts to an isolated working directory and scan it, for example with gitleaks (MIT licence):
gitleaks dir <path> -f json -r findings.json --redact.--redacthides secret values in output. Add custom rules for your internal credential formats, and review every hit by hand, because generic rules both miss passwords and flag test values. The Developer Secret Scanning Triage guide covers ranking findings. - Rotate, then re-plumb the job. For each confirmed credential: rotate it first (assume it has been read), then change the job to obtain the new value safely (next section), test on one host, and roll out. Keep the old definition in the change record, with secrets removed, for rollback.
Replacing embedded credentials
| Where the job runs | Better mechanism | Notes |
|---|---|---|
| systemd service or timer | LoadCredential= or LoadCredentialEncrypted= | Credentials appear as files in $CREDENTIALS_DIRECTORY (for system services also /run/credentials/<unit>), read-only and readable only by the unit's user and root. Encrypted credentials use a TPM2 key, a host key in /var/lib/systemd/credential.secret, or both |
| cron job | Move the job to a systemd timer with credentials, or have the script fetch the secret at run time from a vault using a host or workload identity | If a file must stay, make it owned by the job's account, mode 0600, outside web roots and backups that others can read |
| Windows task as a domain account | Run as a group managed service account, or as a built-in identity if no network access is needed | Removes the stored password. See the Group Managed Service Accounts (gMSA) Guide |
| Windows task needing no network | S4U logon | No password stored; no network or encrypted file access |
| GPP task with a password | Delete the preference item and redeploy without a password | Rotate the exposed account password; deleting the GPO alone does not undo past exposure |
| Cloud API calls from a job | Instance or workload identity instead of static keys | See Dynamic Secrets and Ephemeral Credentials Guide |
Choosing: on a small estate, moving a handful of cron jobs to systemd credentials or gMSAs is a few days of work and needs no new platform. On a large estate, the scale argument favours a central vault and a standard job wrapper that fetches secrets, so every team stops solving the same problem. Either way, rotation comes first: a secret that sat in a crontab for years must be treated as known.
Verify it worked
- Re-scan: run steps 1 to 6 again; fixed jobs must produce no findings and the rotated value must not appear anywhere.
- Old credential is dead: confirm the previous password or key is rejected by the target system.
- Jobs still run: check the next scheduled run of every changed job in its log, the journal or the task history.
- No Password tasks left: a fresh export shows no
LogonTypeofPasswordorInteractiveTokenOrPasswordoutside approved exceptions.
Detection and measurement
Watch for regressions: monitor writes to cron directories and unit paths (auditd or file integrity monitoring, see the Linux File Integrity Monitoring Guide), Windows task creation and updates, and changes to GPO preference files. Run the scan on a schedule and on every configuration management change that touches job definitions. Metrics that show progress:
- Jobs with embedded credentials: count per platform, trending to zero.
- Tasks with stored passwords: Windows tasks with LogonType Password, and GPOs with preference passwords (target zero).
- Exposure age: median age of credentials found in jobs at discovery, a signal of how long secrets go unrotated.
- Time to rotate: days from finding to rotation and job fix.
- Readable secret files: credential files used by jobs that are readable by accounts other than the job's own.
- Jobs with an owner: percentage of non-system jobs with a named owner and recorded identity.
Rollback and common errors
- Job fails after the change: restore the previous definition from the change record but with the new rotated secret, never the old one, then fix the new mechanism.
- systemd credential not found: a missing source makes the unit fail; check the path or credstore name (
/etc/credstore/,/run/credstore/,/usr/lib/credstore/) and that per-user units use credentials encrypted for the user manager. - Task will not start as the new account: tasks using Password or S4U logon need the Log on as a batch job right for the account.
- S4U task cannot reach a share: expected, since S4U has no network access; use a gMSA instead.
- Cron job behaves differently under systemd: cron sets
SHELL=/bin/shand takesHOMEandLOGNAMEfrom the passwd entry; compare the environment before switching schedulers.
Common failure patterns
- Scanning crontab lines only: the secret is in the script or its config file, which never gets scanned.
- Fixing the job without rotating: the credential was readable for years; moving it to a vault leaves the old value valid.
- Swapping a crontab password for an Environment= line: the systemd manual is explicit that environment variables are not suitable for secrets.
- Deleting GPP tasks but not rotating: the password could already have been decrypted from SYSVOL.
- Personal accounts behind production tasks: a task registered with an administrator's own password ties production to a person; see the Service Account Security Guide.
- Reports with clear-text secrets: the findings file is copied into a ticket and becomes the next leak. Redact and store it securely.
Framework mapping
| Framework | Item | How this runbook supports it |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-5, IA-5(7) | Authenticator management; no embedded unencrypted static authenticators |
| NIST SP 800-53 Rev. 5 | CM-8, AC-6 | Inventory of jobs and their identities; least privilege for job accounts |
| NIST CSF 2.0 | PR.AA-01, ID.AM-02 | Service credentials managed; inventory of software and services |
| NIST CSF 2.0 | PR.PS-01, DE.CM-09 | Job definitions under configuration management; monitoring for changes |
| MITRE ATT&CK | T1552.001, T1552.006 | Credentials in files; Group Policy Preferences passwords |
| MITRE ATT&CK | T1053.003, T1053.005, T1053.006 | Cron, Scheduled Task and Systemd Timers as the job mechanisms in scope |
Practitioner checklist
- Export every cron file, user crontab and periodic script, with source paths.
- Export every systemd timer and its service unit, including drop-ins and EnvironmentFile targets.
- Resolve each job to the scripts and config files it reads, and record their permissions.
- Export Windows tasks with
schtasks /query /xmland list every LogonType Password task and its account. - Search SYSVOL preference files for stored passwords and list the GPOs.
- Scan exports with a secret scanner using redaction, then review hits by hand.
- Rotate every confirmed credential before changing the job.
- Move secrets to systemd credentials, a vault, gMSAs or workload identity.
- Monitor job definition paths and re-scan on a schedule.
Standards and references
- systemd: systemd.exec(5) manual, environment and credentials
- cronie: crontab(5) manual
- Microsoft: Task Scheduler schema, logonType simple type
- Microsoft: Security contexts for tasks
- Microsoft: schtasks query
- Microsoft: Security Bulletin MS14-025, Group Policy Preferences
- MITRE ATT&CK: T1552.001 Credentials In Files
- Gitleaks: project documentation
Related NHI Mgmt Group resources: How to Find Privileged Scheduled Tasks and Cron Jobs, How to Find ssh authorized keys across a server fleet, Linux Service Accounts and Keytab Management Guide, Git Secret Scanning Guide.