Copyright (c) 2026 Brad Boegler bradthx@gmail.com
Licensed under the MIT License. See LICENSE.
ndrift is a lightweight Linux file integrity monitor.
File Integrity Monitoring is a crucial part of any application deployment. If your app files are changing, and you are not aware of it, that's a huge problem. I have seen hundreds of examples of sites being compromised for various reasons, and site owners not being aware of it for weeks or even months while in the meantime, client data and pii is being stolen.
FIM is part of just about every modern CNAPP solution, and usually readily available for applications deployed on the cloud. For many sites that are hosted on more traditional hosting deployments such as shared, Dedicated, or VPS environments, FIM is not always easy or implied to already exist by the hosting provider.
I developed ndrift to be a super simple, but effective FIM utility that can be easily deployed to any linux hosted web application and provide a robust FIM solution.
ndrift builds a trusted baseline of file metadata and content hashes, then compares future scans to detect drift such as modified files, added files, deleted files, permission changes, ownership changes, timestamp only changes, and filesystem attribute changes.
- Recursively scans one or more monitored directories.
- Applies include rules and exclude rules to control scan scope.
- Stores a signed baseline in local JSON files.
- Detects file drift on each scan with both console output and JSON output.
- Writes logs with rotation.
- Supports periodic scan mode with an interval loop.
- Supports deployment approval workflow for safe baseline updates.
- Returns script friendly exit codes.
On init or baseline update, ndrift records per file data:
- absolute path
- size
- mtime in nanoseconds
- SHA256 content hash
- mode bits
- uid and gid
- owner and group names
- lsattr flags
The baseline file includes additional integrity data:
- baseline schema version
- monitored directory list
- configuration file hash
- ndrift program hash
- creation timestamp
- baseline reason text
ndrift signs the canonical baseline JSON payload with HMAC SHA256 and writes a detached signature file.
- Baseline JSON path default: /var/lib/ndrift/ndrift-baseline.json
- Detached signature path default: /var/lib/ndrift/ndrift-baseline.json.sig
- Secret signing key path default: /var/lib/ndrift/ndrift-baseline.key
On scan, signature verification runs before drift detection. If verification fails, ndrift returns exit code 2.
ndrift also signs deployment state and audit data with a detached HMAC signature.
- State file path default: /var/lib/ndrift/ndrift-state.json
- State signature path default: /var/lib/ndrift/ndrift-state.json.sig
- Signature key path default: /var/lib/ndrift/ndrift-baseline.key
State signature verification runs before approval and deployment trust decisions. If verification fails, ndrift returns exit code 2.
Each scan executes these phases:
- Load configuration.
- Validate monitored paths.
- Verify baseline signature.
- Recalculate config hash and program hash, compare with baseline.
- Build current snapshot.
- Compare current snapshot to baseline.
- Emit findings, warnings, errors, and summary.
- Persist latest report JSON.
- Trigger optional integrations.
- ADDED
- DELETED
- MODIFIED
- MOD_PERMS
- MOD_OWNER
- MOD_ATTR
- MOD_TIME
- MOD_CONFIG
- MOD_PROGRAM
ndrift includes simple, effective optimizations:
- If hash on metadata change only is true, hash reuse occurs when size and mtime are unchanged.
- If file size exceeds max file size mb, ndrift records a size limit sentinel instead of hashing content.
- throttle ms can slow scan pace to reduce host impact.
If scan_updates_baseline is true, ndrift updates baseline after each scan.
- New changes are typically reported once.
- Later scans compare against the most recent scan state.
- This mode is useful for low noise monitoring workflows.
- This mode is less strict than fixed baseline monitoring.
- Linux
- Python 3.6 or newer
- for Python 3.6 only: dataclasses backport package
- optional: lsattr for filesystem attribute checks
- optional: AWS CLI for object storage report upload
chmod +x ./ndrift
./ndrift init /path/to/monitor --config ./ndrift.confsudo install -o root -g root -m 0755 ./ndrift /usr/local/bin/ndrift
sudo install -d -o root -g root -m 0700 /var/lib/ndrift
sudo install -d -o root -g root -m 0750 /var/log/ndrift
sudo install -d -o root -g root -m 0755 /etc/ndrift
sudo install -o root -g root -m 0600 ./ndrift.conf /etc/ndrift/ndrift.conf- executable: /usr/local/bin/ndrift
- config: /etc/ndrift/ndrift.conf
- baseline: /var/lib/ndrift/ndrift-baseline.json
- signature: /var/lib/ndrift/ndrift-baseline.json.sig
- signing key: /var/lib/ndrift/ndrift-baseline.key
- state: /var/lib/ndrift/ndrift-state.json
- state signature: /var/lib/ndrift/ndrift-state.json.sig
- lock file: /var/lib/ndrift/ndrift.lock
- last report: /var/lib/ndrift/ndrift-report.json
- log: /var/log/ndrift/ndrift.log
Use this flow to get ndrift running on a real directory, including cron scheduling.
- Create the first baseline.
sudo /usr/local/bin/ndrift init /path/to/monitor --config /etc/ndrift/ndrift.conf- Run an immediate scan to confirm it works.
sudo /usr/local/bin/ndrift scan --config /etc/ndrift/ndrift.conf- Review the latest report.
sudo /usr/local/bin/ndrift report --config /etc/ndrift/ndrift.conf- Install a cron entry manually.
CRON_LINE="$(sudo /usr/local/bin/ndrift cron --config /etc/ndrift/ndrift.conf)"
(sudo crontab -l 2>/dev/null; echo "$CRON_LINE") | sudo crontab -
sudo crontab -l | grep ndrift- Validate that scans are running from cron.
sudo tail -n 50 /var/log/ndrift/ndrift.log
sudo /usr/local/bin/ndrift report --config /etc/ndrift/ndrift.conf- Optional: run continuous watch mode for debugging.
sudo /usr/local/bin/ndrift watch --interval 60 --config /etc/ndrift/ndrift.confIf you change cron_schedule in config, run the cron command again and replace the existing crontab entry.
- init: creates baseline and signature, also writes state audit event
- scan: verifies signature and compares current state with baseline
- update-baseline: rebuilds baseline with required reason text
- report: prints the latest saved report
- watch: runs periodic scans in loop
- deploy-start: opens deployment window
- approve: records deployment approval reason
- deploy-end: closes deployment window
- cron: prints a cron entry using the configured schedule
System default config path: /etc/ndrift/ndrift.conf.
Template in this repository: ndrift.conf.
- directories: comma separated monitored roots
- baseline_path: trusted baseline JSON path
- signature_path: detached signature path
- signature_key_path: HMAC key path
- state_path: deployment and audit state path
- state_signature_path: detached signature path for state integrity
- lock_path: process lock file path for overlap protection
- last_report_path: latest scan report path
- log_path: rotating log file path
- include_patterns: comma separated glob rules for files to scan
- exclude_patterns: comma separated rules for content to skip
- follow_symlinks: true or false
- max_file_size_mb: hash size ceiling in MB
- hash_on_metadata_change_only: true or false
- throttle_ms: delay per file in milliseconds
- cron_schedule: schedule expression for cron helper output
- email_to: alert mailbox
- smtp_host: SMTP relay
- slack_webhook: webhook URL
- s3_bucket: object storage destination for report upload
Default cron schedule is */30 * * * * which runs every 30 minutes.
- allow_read_errors false: treat file read failures as exit code 2
- allow_read_errors true: continue scan and include errors in report
- scan_updates_baseline false: keep a fixed baseline until init or update-baseline
- scan_updates_baseline true: write a new baseline after each scan so repeated ADDED alerts are reduced
- 0: scan completed with no findings
- 1: scan completed and findings were detected
- 2: runtime error, integrity verification failure for baseline or state, or strict read error condition
Each scan writes a JSON report to last report path with:
- time
- directories
- summary
- findings
- warnings
- errors
- stats
Example console style output:
[MODIFIED] /path/to/file.php hash changed
[MOD_PERMS] /path/to/file.php 0644 -> 0666
{"MODIFIED": 1, "MOD_PERMS": 1}
Use this workflow to allow expected changes while preserving auditability.
/usr/local/bin/ndrift deploy-start --config /etc/ndrift/ndrift.conf
/usr/local/bin/ndrift approve --reason "planned release" --config /etc/ndrift/ndrift.conf
/usr/local/bin/ndrift update-baseline --reason "post release baseline" --require-signature --config /etc/ndrift/ndrift.conf
/usr/local/bin/ndrift deploy-end --config /etc/ndrift/ndrift.confDuring active deployment mode, baseline update requires at least one approval after deployment start.
Generate a schedule line:
/usr/local/bin/ndrift cron --config /etc/ndrift/ndrift.confThen place the output into the desired cron context.
- baseline signature verification before scan comparison
- state signature verification before deployment approval checks and state load
- advisory lock file guard prevents overlapping runs from racing state and baseline updates
- baseline and state write operations use atomic replace
- baseline, state, signature, and key files are written with mode 600
- baseline location warning if placed inside monitored roots
- symlink following disabled by default
- configuration file hash drift detection
- program hash drift detection
- strict exit codes for automation and alert pipelines
- run ndrift using a dedicated low privilege account
- grant read access to monitored files only as needed
- deny write access to baseline, signature, and key for non root users
- install ndrift in a root owned read only location
- store config and state in protected directories
- ship logs and reports to central monitoring
- test alert channels and error paths routinely
- Baseline signing currently uses a local symmetric key. If key material is compromised, signature trust is compromised.
- Program hash checks can detect drift, but do not replace package signature verification.
- Email, Slack, and object storage integrations depend on network and external credential security.
- scan_updates_baseline true reduces repeated alerts, but also reduces strict persistence of change evidence across scans. This option is useful for very specific deployments and integrations. Only enable if you understand this and have a very specific use case.
- ndrift: executable CLI program
- ndrift.conf: default configuration template