Build a File Integrity Monitor
Build a CLI tool that detects unauthorized file changes using SHA-256 hashing.
Introduction
30 Second Summary
Every time you lock your front door, you trust that nothing inside will be disturbed while you are away. Your computer's files need the same kind of protection, because someone could quietly change a critical file and you would never notice.
In this project, you will build a command-line security tool called File Guardian using Python. File Guardian creates a unique fingerprint of every file in a directory using SHA-256 hashing, then compares those fingerprints later to catch any modifications, additions, or deletions.
What You'll Build
Run File Guardian against a folder you have tampered with and watch it instantly flag every modified, added, and deleted file in a formatted alert report.
By the end of this project, you'll have:
- A baseline snapshot of any directory taken with a single command, saved as a human-readable JSON file you can inspect.
- An integrity check that compares the current state of your files against the saved baseline.
- A formatted alert report showing exactly which files were modified, added, or deleted.
- Secret Mission: Add a real-time watch mode that continuously monitors for changes and alerts you the moment something is tampered with.
Are there any prerequisites?
No prerequisites are needed for this project. Everything you need is built into Python's standard library, and this guide walks you through setting up your workspace from scratch.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters.
Set Up Your Project
Before you can build File Guardian, you need a workspace and a set of files to monitor. Without test files, you would have nothing safe to tamper with later when you want to prove the tool actually catches changes.
In this step, you'll confirm Python is ready, create the project directory with sample files, and prepare the file_guardian.py file where all the code will go.
In this step, get ready to:
- Confirm Python 3 is installed.
- Create the project directory with test files containing mock content.
- Prepare file_guardian.py in VS Code.
Confirm Python is installed
File Guardian relies entirely on Python's standard library for cryptographic hashing and file system traversal. Let's make sure Python 3 is available on your machine.
- Check your Python version by running this command in your terminal:
python3 --version
✔️ I see a version number
You should see something like Python 3.x.x printed in the terminal. Any Python 3 version works for this project.
ⓧ Command not found
Python 3 is not installed on your machine. Install it by running:
sudo apt update && sudo apt install python3
- After the install completes, run python3 --version again to confirm it worked.
Create the project workspace and test files
A file integrity monitor needs files to watch. You'll create a project folder with a test_files subdirectory containing three mock files that simulate the kind of sensitive data you would protect in a real system.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create the project directory with a test_files subdirectory, then move into the project folder by running these commands:
mkdir -p file_guardian_project/test_files
cd file_guardian_project
- Confirm the directory structure was created by running:
ls
You should see test_files listed in the output.
- Create the three sample files inside test_files by running these commands:
cat > test_files/passwords.txt << 'EOF'
# Mock passwords file
admin=supersecret
user=password123
root=toor
EOF
cat > test_files/config.ini << 'EOF'
[settings]
debug=false
max_connections=100
host=localhost
EOF
cat > test_files/notes.txt << 'EOF'
Project notes
TODO: review access logs
Last updated: 2026-08-22
EOF
What are these test files?
Each file simulates a different type of sensitive data you might find on a real server.
- passwords.txt holds mock credentials. An attacker modifying this file would be a serious breach.
- config.ini holds application settings. Tampered config could redirect traffic or enable debug mode in production.
- notes.txt is a general document. It shows the monitor watches all files, not just the obviously sensitive ones.
- Confirm all three files were created by running:
ls test_files
You should see config.ini, notes.txt, and passwords.txt listed in the output.
Not seeing all three files?
- Make sure you ran each cat command in full. If you copied only part of the block, a file may be missing or empty.
- Check that you are inside the file_guardian_project directory by running pwd. The path should end with file_guardian_project.
- help me troubleshoot missing test files after running the cat commands
Create file_guardian.py and open the project in VS Code
Now you need the Python file where all of File Guardian's code will live. You'll also open the project in VS Code so you can edit the file easily in the next steps.
- Create an empty file_guardian.py file by running this command:
touch file_guardian.py
- Open the project folder in VS Code by running:
code .
VS Code opens with the file_guardian_project folder loaded in the Explorer sidebar. You should see file_guardian.py and the test_files folder listed there.
Before you run the next command, what files and folders do you expect to see listed inside test_files?
- Verify your complete project structure by running:
ls test_files
You should see config.ini, notes.txt, and passwords.txt. Three files ready to be monitored.
VS Code didn't open?
- If code . gives you a "command not found" error, open VS Code manually, click File in the top menu bar, then click Open Folder, and select the file_guardian_project folder on your Desktop.
- help me open my project folder in VS Code from the terminal
Your workspace is locked and loaded. Next up, you'll write a function that creates a cryptographic fingerprint of a file using SHA-256 hashing.
Hash Your First File
Your project workspace is ready with three test files waiting to be monitored. Now it's time to build the core mechanism that powers every file integrity monitor.
Every file on your computer can be reduced to a unique fingerprint called a cryptographic hash. If even one character in the file changes, the fingerprint changes completely. This property is the foundation of how security tools detect unauthorized modifications.
In this step, get ready to:
- Write a function that computes SHA-256 hashes of any file.
- Prove that changing a single character produces a completely different hash.
Write the hash_file() function
SHA-256 is a cryptographic hash algorithm that takes any input and produces a fixed-length 64-character hex string. Python's hashlib module provides it out of the box, so you do not need to install anything.
- Switch back to file_guardian.py in VS Code and add the following code:
#!/usr/bin/env python3
"""File Guardian - A File Integrity Monitor"""
import hashlib
def hash_file(filepath):
"""Generate SHA-256 hash of a file."""
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256.update(chunk)
return sha256.hexdigest()
# Quick test
print(hash_file("test_files/passwords.txt"))
What does this code do?
- hashlib.sha256() creates a new SHA-256 hash object that you will feed data into.
- open(filepath, "rb") opens the file in binary mode. Hashing works on raw bytes, not text characters.
- iter(lambda: f.read(8192), b"") reads the file in 8192-byte chunks instead of loading the entire file into memory at once. This makes it safe for large files.
- sha256.update(chunk) feeds each chunk into the hash object. Calling it repeatedly is equivalent to hashing all the data at once.
- sha256.hexdigest() returns the final hash as a 64-character hex string.
The last two lines are a quick test that hashes test_files/passwords.txt and prints the result.
- Save file_guardian.py.
- Run the script by entering this command in your terminal:
python3 file_guardian.py
You should see a 64-character hex string printed to the terminal, something like a1b2c3d4e5...f6a7b8. Your exact hash will be different, but it will always be exactly 64 characters.
Not seeing a hash?
- If you see FileNotFoundError, make sure you are running the command from inside the file_guardian_project directory, not from inside test_files.
- If you see a SyntaxError, double check that the lambda line matches exactly. The b"" at the end is a bytes literal (lowercase b, then two double quotes with nothing between them).
Still stuck? Help me debug why python3 file_guardian.py is not printing a hash.
Prove the hash catches changes
Seeing a hash once is interesting, but the real power is in what happens when a file changes. Even the smallest edit to a file produces a completely different hash. This is called the avalanche effect.
- In VS Code, open test_files/passwords.txt.
- Change supersecret to supersecrets (add one letter at the end).
- Save passwords.txt.
Before you run the script again, what do you think the new hash will look like? Will it be similar to the first one, or completely different?
- Run the script again to find out:
python3 file_guardian.py
The hash is completely different. Not one or two characters changed. The entire 64-character string is new. That is the avalanche effect in action.
Why does one letter change the whole hash?
Cryptographic hash functions are designed so that similar inputs produce wildly different outputs. This makes it impossible for an attacker to make a "small" change that slips past detection.
If hashes only changed slightly when files changed, an attacker could tweak a file until the hash happened to match. The avalanche effect closes that door completely.
- In passwords.txt, change supersecrets back to supersecret and save the file. This keeps your test files clean for the next step.
✔️ Awesome, I've got everything!
Great. Double check you have saved file_guardian.py and reverted passwords.txt back to its original content.
ⓧ I'd like to double check the full code
Here is the complete file_guardian.py at this point. Make sure yours matches:
#!/usr/bin/env python3
"""File Guardian - A File Integrity Monitor"""
import hashlib
def hash_file(filepath):
"""Generate SHA-256 hash of a file."""
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256.update(chunk)
return sha256.hexdigest()
# Quick test
print(hash_file("test_files/passwords.txt"))
Also confirm that test_files/passwords.txt is back to its original content:
# Mock passwords file
admin=supersecret
user=password123
root=toor
You can now fingerprint any file and prove that even the smallest change gets caught. Next up, you will scan an entire directory of files and save a baseline snapshot that you can compare against later.
Scan and Save a Baseline Snapshot
Your hash_file() function can fingerprint a single file. That is a great start, but a real security monitor needs to fingerprint every file in a directory at once.
Without a saved record of those fingerprints, you have nothing to compare against later. In this step, you will scan an entire directory tree, collect every file's SHA-256 hash, and save the results to a JSON file that acts as your trusted baseline.
In this step, get ready to:
- Scan an entire directory and hash every file in it.
- Save the hash results as a JSON baseline snapshot with a timestamp.
- Run the script and confirm the baseline file is created.
Write the directory scanner
Right now your script only knows how to hash one file at a time. You need a function that walks through every file in a directory and collects all the hashes into a single dictionary.
- In file_guardian.py, find the import hashlib line at the top of the file.
- Add these three imports directly below it:
import os
import json
from datetime import datetime
- Scroll down past the hash_file() function.
- Add the scan_directory() function below it by copying this code:
def scan_directory(directory):
"""Walk a directory and return a dict of filepath to hash."""
file_hashes = {}
for dirpath, dirnames, filenames in os.walk(directory):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
if os.path.isfile(filepath):
try:
file_hashes[filepath] = hash_file(filepath)
except (PermissionError, OSError) as e:
print(f"Warning: Could not read {filepath}: {e}")
return file_hashes
What does this code do?
- os.walk(directory) walks through every subdirectory and file in the given path. For each directory it visits, it yields the directory path, a list of subdirectories, and a list of filenames.
- os.path.join() builds the full file path from the directory and filename, so the hash dictionary stores paths like test_files/passwords.txt instead of just passwords.txt.
- The try/except block catches files the script cannot read (locked or permission-restricted files). Instead of crashing, it prints a warning and keeps scanning.
Time to verify the scanner works before moving on.
- Scroll to the bottom of file_guardian.py and find these two lines:
# Quick test
print(hash_file("test_files/passwords.txt"))
- Replace both lines with this:
# Test
print(scan_directory("test_files"))
- Save file_guardian.py.
- Run the script in your terminal by running this command:
python3 file_guardian.py
You should see a dictionary printed with three entries. Each key is a file path like test_files/passwords.txt, and each value is a 64-character hash string.
Seeing a NameError or empty output?
- If you see NameError: name 'os' is not defined, double check you added the import os line below import hashlib at the top of the file.
- If the output is an empty dictionary {}, make sure you are passing "test_files" (not a different path) and that the test_files folder still has files in it.
- Still stuck? Help me debug my scan_directory function. It should print a dictionary of three file paths and their SHA-256 hashes.
Save the baseline to JSON
Printing hashes to the terminal is useful for testing, but those results vanish the moment you close the window. To compare file state over time, you need to save the snapshot to a file you can reload later.
- In file_guardian.py, add the save_baseline() function directly below scan_directory():
def save_baseline(file_hashes, baseline_path):
"""Save baseline snapshot to a JSON file."""
baseline = {
"timestamp": datetime.now().isoformat(),
"file_count": len(file_hashes),
"files": file_hashes,
}
with open(baseline_path, "w", encoding="utf-8") as f:
json.dump(baseline, f, indent=2)
print(f"Baseline saved to '{baseline_path}'")
print(f" Files recorded: {len(file_hashes)}")
print(f" Timestamp: {baseline['timestamp']}")
What does this code do?
- The function wraps the hash dictionary inside a larger dictionary that also records a timestamp and file_count. The timestamp tells you exactly when the baseline was taken.
- json.dump() writes the dictionary to a file as human-readable JSON. The indent=2 argument formats it with nice spacing so you can open it and read it yourself.
- The three print() lines give you a quick confirmation in the terminal so you know the save succeeded without having to open the file.
Now update the test code at the bottom so it calls both functions.
- Find these lines at the bottom of your file:
# Test
print(scan_directory("test_files"))
- Replace both lines with this:
# Test
file_hashes = scan_directory("test_files")
save_baseline(file_hashes, "baseline.json")
- Save file_guardian.py.
Before you run the script, what do you think the output will look like? Will it print a dictionary like before, or something different?
- Run the script by running this command:
python3 file_guardian.py
This time, instead of a raw dictionary, you should see a clean confirmation message showing the baseline file path, a file count of 3, and a timestamp.
- Confirm the baseline file was created by running:
ls
You should see baseline.json listed alongside file_guardian.py and the test_files directory.
- Open baseline.json in VS Code by clicking on it in the Explorer sidebar.
You should see a JSON object with a timestamp, a file_count of 3, and a files object containing all three test file paths paired with their 64-character hashes.
Don't see baseline.json?
- Make sure you saved file_guardian.py before running the script. Unsaved changes mean the old test code ran instead.
- If you see NameError: name 'json' is not defined, confirm you added import json at the top of the file.
- Check that your test code passes two arguments to save_baseline(): the file_hashes variable and the string "baseline.json".
- Still stuck? Help me debug my save_baseline function. It should create a baseline.json file with hashes for three test files.
✔️ Awesome, I've got everything!
Great. Double check you have saved file_guardian.py and that baseline.json exists in your project folder.
ⓧ I'd like to double check the full code
Scroll up through your file_guardian.py and confirm each section is present, in this order:
- The shebang line and docstring at the very top.
- Four import lines: hashlib, os, json, and from datetime import datetime.
- The hash_file() function from the previous step.
- The scan_directory() function with os.walk() and error handling.
- The save_baseline() function with json.dump() and three print() lines.
- The test code at the bottom: scan_directory("test_files") followed by save_baseline(file_hashes, "baseline.json").
You now have a trusted snapshot of your directory locked in a file. Next up, you will build the change detector that compares the current state of your files against this baseline and catches anything that does not match.
Build the Change Detector
In the last step, you saved a trusted baseline snapshot of your test files. Every file's SHA-256 fingerprint is locked inside baseline.json.
The snapshot alone is not enough. You need a way to reload that baseline and compare it against the current state of your files. That comparison is the heart of integrity monitoring.
In this step, get ready to:
- Write functions to load the baseline and compare file hashes against it.
- Build a formatted report that flags modified, added, and deleted files.
- Run an integrity check and see whether your tool catches any changes.
Write the baseline loader and integrity checker
Your baseline snapshot is saved as JSON on disk, but the script has no way to read it back in. You also need a function that re-scans the directory and compares every file's current hash to the stored one.
- In VS Code, open file_guardian.py.
- Find the closing line of the save_baseline() function. Add the following two functions directly below it, before the # Test comment:
def load_baseline(baseline_path):
"""Load a baseline snapshot from a JSON file."""
with open(baseline_path, "r", encoding="utf-8") as f:
return json.load(f)
def check_integrity(directory, baseline_path):
"""Compare current state against baseline and report changes."""
# Load the saved baseline
baseline = load_baseline(baseline_path)
baseline_files = baseline["files"]
# Scan the directory again right now
current_files = scan_directory(directory)
# Three categories of change
modified = []
added = []
deleted = []
# Check every current file against the baseline
for filepath, current_hash in current_files.items():
if filepath not in baseline_files:
added.append(filepath)
elif current_hash != baseline_files[filepath]:
modified.append(filepath)
# Check for files that were in the baseline but are now gone
for filepath in baseline_files:
if filepath not in current_files:
deleted.append(filepath)
return modified, added, deleted, baseline["timestamp"]
What does this code do?
load_baseline() reads baseline.json back into a Python dictionary using json.load(). This gives you back the same timestamp, file count, and hash dictionary you saved earlier.
check_integrity() does the real detective work. It re-scans the directory with scan_directory() and walks through every current file. If a file exists now but was not in the baseline, it is added. If a file's hash has changed, it is modified. A second loop catches files that were in the baseline but are now gone (deleted).
- Save file_guardian.py.
You will not run the script just yet. The next function turns those raw lists into a readable report.
Write the report printer
check_integrity() returns three lists and a timestamp, but raw lists are hard to read. A formatted report makes it obvious what changed at a glance.
- In file_guardian.py, add the following function directly below check_integrity(), before the # Test comment:
def print_report(modified, added, deleted, baseline_timestamp):
"""Print a formatted integrity report."""
# Report header
print("\n" + "=" * 50)
print(" FILE INTEGRITY REPORT")
print("=" * 50)
print(f" Baseline taken: {baseline_timestamp}")
print(f" Check performed: {datetime.now().isoformat()}")
print("-" * 50)
total_changes = len(modified) + len(added) + len(deleted)
if total_changes == 0:
# No changes found
print("\n [OK] All files match the baseline.")
else:
print(f"\n [ALERT] {total_changes} change(s) detected!\n")
if modified:
print(f" MODIFIED ({len(modified)}):")
for filepath in modified:
print(f" [M] {filepath}")
if added:
print(f"\n ADDED ({len(added)}):")
for filepath in added:
print(f" [A] {filepath}")
if deleted:
print(f"\n DELETED ({len(deleted)}):")
for filepath in deleted:
print(f" [D] {filepath}")
print("\n" + "=" * 50)
What does this code do?
print_report() draws a bordered report with the baseline timestamp and the current time. It counts the total changes across all three lists.
- If the total is zero, it prints [OK] All files match the baseline.
- If changes exist, it prints an [ALERT] line and lists each change with a marker: [M] for modified, [A] for added, and [D] for deleted.
- Save file_guardian.py.
Run the integrity check
Your three new functions are ready, but the bottom of the script still runs the old baseline-saving code. Time to swap it out so the script runs an integrity check instead.
- In file_guardian.py, scroll to the bottom of the file and find these lines:
# Test
file_hashes = scan_directory("test_files")
save_baseline(file_hashes, "baseline.json")
- Replace those three lines with the following code:
# Test
modified, added, deleted, timestamp = check_integrity("test_files", "baseline.json")
print_report(modified, added, deleted, timestamp)
- Save file_guardian.py.
Before you run this, think about what the report will show. Have you changed any of the test files since you took the baseline in the last step?
- Run the integrity check by running this command:
python3 file_guardian.py
You should see a formatted report with [OK] All files match the baseline. printed between two lines of equals signs.
Why does it say OK?
The check is working correctly. You have not touched any of the test files since the baseline was saved, so every hash still matches. The tool confirms there are zero changes.
But this also means you have not proven the tool actually catches tampering. Right now you only know it can report "all clear." The next step will put it to the real test.
Seeing an error instead of the report?
- If you see FileNotFoundError mentioning baseline.json, make sure baseline.json exists in your project directory from the previous step. You can regenerate it by temporarily restoring the old test code, running the script, then swapping back.
- If you see NameError, check that you placed load_baseline() and check_integrity() above the test code at the bottom of the file. Python reads top to bottom, so functions must be defined before they are called.
- Still stuck? Help me debug the error I'm seeing when running file_guardian.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved file_guardian.py.
ⓧ I'd like to double check the full code
Your file_guardian.py is getting long, so scroll through it from top to bottom and confirm these sections appear in this order:
- The imports at the top: hashlib, os, json, and datetime.
- The hash_file() function (from Step 2).
- The scan_directory() and save_baseline() functions (from Step 3).
- The load_baseline() and check_integrity() functions (added in this step).
- The print_report() function (added in this step).
- The updated # Test block at the bottom calling check_integrity() and print_report().
Your change detector works, but you have only proven it can say "all clear." Next up, you will tamper with the test files yourself and watch the alerts fire.
Catch the Intruder
In the last step, your integrity check came back clean. The report showed [OK] All files match the baseline. because nothing had changed since you took the snapshot.
That clean report reveals a gap. You have not proven the tool actually catches tampering. In this step, you will play the attacker, break things in three different ways, and then upgrade File Guardian into a proper CLI tool.
In this step, get ready to:
- Tamper with test files to simulate a real attack.
- Wrap the tool in a proper CLI using argparse subcommands.
- Catch all three types of file changes with a single command.
Simulate an attack on the test files
An attacker might modify sensitive data, plant new malicious files, or delete logs to cover their tracks. You will simulate all three scenarios against your test directory.
- Tamper with your test files by running these commands in the terminal:
# Modify an existing file
echo '[TAMPERED] attacker was here' >> test_files/passwords.txt
# Plant a new malicious file
echo '#!/bin/bash' > test_files/malware.sh
echo "echo 'malware payload'" >> test_files/malware.sh
# Delete a file to cover tracks
rm test_files/notes.txt
What do these commands do?
- echo '...' >> appends a line to passwords.txt without overwriting the rest. Even one extra character changes the file's SHA-256 hash completely.
- echo '...' > creates a brand-new file called malware.sh that did not exist in the baseline.
- rm deletes notes.txt entirely. The baseline still records it, so the monitor will flag it as missing.
- Verify the file system changes by running:
ls test_files
You should see config.ini, malware.sh, and passwords.txt. Notice that notes.txt is gone.
Before you run the script, what do you think the integrity report will show this time? Will it still say everything is OK?
- Run the script to check the integrity of your test files:
python3 file_guardian.py
The report fires with [ALERT] 3 change(s) detected! showing a modified file, an added file, and a deleted file. Your monitor works.
Still seeing all files match?
- Make sure you ran the tamper commands in the correct directory. The echo and rm commands should target files inside test_files/.
- Run ls test_files to confirm malware.sh appears and notes.txt is missing.
- Check that baseline.json still exists. If you accidentally deleted or re-created it, the hashes will match the tampered files.
Why isn't my integrity check detecting any changes after I tampered with the files?
Build the CLI with argparse
Professional security tools use subcommands like init and check. Python's argparse module lets you define subcommands so your script works like a real CLI tool.
- In file_guardian.py, find the import lines at the top of the file. Add import argparse and import sys directly below import json (before the from datetime line).
- Scroll to the bottom of the file and delete the three-line test block: the # Test comment and the two lines below it.
- Replace the deleted test code with the start of a new function. Paste this code at the bottom of your file:
def main():
# Set up the argument parser
parser = argparse.ArgumentParser(
prog="file_guardian",
description="File Guardian - A File Integrity Monitor",
)
subparsers = parser.add_subparsers(dest="command", help="Available commands")
# Define the 'init' subcommand
init_parser = subparsers.add_parser("init", help="Take a baseline snapshot")
init_parser.add_argument("directory", help="Directory to monitor")
init_parser.add_argument(
"-o",
"--output",
default="baseline.json",
help="Baseline file path (default: baseline.json)",
)
# Define the 'check' subcommand
check_parser = subparsers.add_parser(
"check", help="Check files against baseline"
)
check_parser.add_argument("directory", help="Directory to check")
check_parser.add_argument(
"-b",
"--baseline",
default="baseline.json",
help="Baseline file path (default: baseline.json)",
)
What does this code do?
- ArgumentParser creates the top-level CLI with a program name and description.
- add_subparsers() enables subcommands. Each subcommand gets its own parser with its own set of arguments.
- The init subcommand takes a directory to scan. The check subcommand takes a directory and an optional baseline file path.
- Save file_guardian.py.
- Add the command handling logic directly below the code you just pasted. This code continues inside main(), so keep the same indentation level:
args = parser.parse_args()
if args.command == "init":
# Validate directory, then scan and save
if not os.path.isdir(args.directory):
print(f"Error: '{args.directory}' is not a valid directory.")
sys.exit(1)
print(f"Scanning '{args.directory}'...")
file_hashes = scan_directory(args.directory)
save_baseline(file_hashes, args.output)
elif args.command == "check":
# Validate inputs, then run integrity check
if not os.path.isfile(args.baseline):
print(f"Error: Baseline file '{args.baseline}' not found.")
print("Run 'python3 file_guardian.py init <directory>' first.")
sys.exit(1)
if not os.path.isdir(args.directory):
print(f"Error: '{args.directory}' is not a valid directory.")
sys.exit(1)
modified, added, deleted, timestamp = check_integrity(
args.directory, args.baseline
)
print_report(modified, added, deleted, timestamp)
else:
parser.print_help()
if __name__ == "__main__":
main()
What does this code do?
- parser.parse_args() reads whatever the user typed on the command line and figures out which subcommand they chose.
- The init handler validates the directory exists, scans it, and saves the baseline. The check handler validates both the baseline file and directory exist before running the integrity check.
- If no subcommand is given, parser.print_help() shows usage instructions.
- The if __name__ == "__main__" block ensures main() only runs when you execute the script directly, not when it is imported.
- Save file_guardian.py.
Seeing a syntax error?
- Make sure the command handling code is indented inside def main():. The args = parser.parse_args() line should be at the same indentation level as the parser = argparse.ArgumentParser( line above it.
- Check that if __name__ == "__main__": is at the very bottom of the file with no indentation. It sits outside the main() function.
- Confirm you deleted all three lines of the old test block (the # Test comment and both lines below it). Leftover code can cause conflicts.
Help me fix a syntax error in my file_guardian.py argparse setup.
Catch the intruder with the new CLI
Your tool now accepts subcommands. The check subcommand takes the directory to scan and compares it against the saved baseline.
Before you run this command, do you think the CLI version will detect the same three changes? Or could the refactor have broken something?
- Run the integrity check using the new CLI:
python3 file_guardian.py check test_files
You should see [ALERT] 3 change(s) detected! with three entries: [M] test_files/passwords.txt for the modified file, [A] test_files/malware.sh for the added file, and [D] test_files/notes.txt for the deleted file.
Seeing an error instead of the report?
- If you see Baseline file 'baseline.json' not found, your baseline file may have been deleted. Run python3 file_guardian.py init test_files to create a fresh baseline, but note that it will reflect the tampered state.
- If the script prints help text instead of a report, make sure you included check as the first argument: python3 file_guardian.py check test_files.
Help me debug why my file_guardian.py check command is not working.
✔️ Awesome, I've got everything!
Great. Double check you have saved file_guardian.py.
ⓧ I'd like to double check the full code
Your file_guardian.py should contain these sections in order. Scroll through the file and confirm each one is present.
- Imports: hashlib, os, json, argparse, sys, and from datetime import datetime.
- hash_file() function.
- scan_directory() function.
- save_baseline() function.
- load_baseline() function.
- check_integrity() function.
- print_report() function.
- main() function with parser setup and command handling for init and check.
- if __name__ == "__main__": main() at the very bottom with no indentation.
Make sure the old test code (the # Test block) is completely gone. It should have been replaced by main().
You just caught a simulated attacker red-handed. Your File Guardian is a fully working CLI security tool. Next up, there is a Secret Mission waiting if you want to push your tool even further.
Secret mission
Add Real-Time Watch Mode
Your File Guardian can take snapshots and check for changes on demand. But real security monitoring does not wait for you to remember to run a command. In this mission, you will add a watch mode that continuously monitors your directory and fires alerts the moment something is tampered with.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine. There are no cloud resources or ongoing costs.
Resources you used:
- file_guardian_project directory on your Desktop, containing file_guardian.py, baseline.json, and the test_files/ subdirectory.
Keep everything running
No action needed. Choose this if you want to keep experimenting with File Guardian or monitor other directories on your machine.
- Your file_guardian_project directory stays on your Desktop.
- You can point File Guardian at any directory on your system by running python3 file_guardian.py init <directory> with a different path.
- If watch mode is still running in a terminal, you can stop it anytime with Ctrl+C.
Pause - I'll come back to this later
Since everything is local files, there is nothing to shut down or suspend. Your files will be waiting for you when you come back.
- If watch mode is still running in a terminal, press Ctrl+C to stop it and free up that terminal.
- Your file_guardian_project directory, including file_guardian.py and baseline.json, stays on your Desktop until you decide to delete it.
Delete - I don't want to use this again
Remove all project files from your machine. This deletes everything you built in this project.
- If watch mode is still running, press Ctrl+C to stop it first.
- Navigate to your Desktop and delete the project directory by running these commands:
cd ~/Desktop
rm -rf file_guardian_project
- Confirm the directory is gone by running:
ls ~/Desktop
You should no longer see file_guardian_project in the output.
Nice Work!
Nice Work!
You just built a fully functional File Integrity Monitor from scratch using nothing but Python's standard library. File Guardian can fingerprint an entire directory, save a trusted baseline, and catch any unauthorized modification, addition, or deletion.
What you learned:
- Used SHA-256 cryptographic hashing to generate unique fingerprints of file contents, and proved that changing a single character produces a completely different hash.
- Built a baseline snapshot system that traverses a directory with os.walk(), records every file's hash in a JSON file, and compares the current state against it to categorize changes as modified, added, or deleted.
- Designed a professional CLI tool with argparse subcommands (init and check), following the same command pattern used by tools like Git and Docker.
- Secret Mission: Added a real-time watch mode that continuously monitors a directory and fires timestamped alerts the moment a file is tampered with.
Ready to quiz yourself?