Linux Security - File Permissions Series
Two hands-on sessions covering how to read, predict, build, audit, and harden file permissions on a Linux system - from the basics of rwxrwxrwx to real-world security misconfigurations.
Open a terminal and run these commands before starting any exercise. They create the working directory, test users, and a shared group that all exercises depend on.
# 1. Create the working directory mkdir -p ~/perm_labs && cd ~/perm_labs # 2. Create three test users sudo useradd -m -s /bin/bash alice sudo useradd -m -s /bin/bash bob sudo useradd -m -s /bin/bash charlie # 3. Set passwords echo "alice:alice123" | sudo chpasswd echo "bob:bob123" | sudo chpasswd echo "charlie:charlie123" | sudo chpasswd # 4. Create shared group sudo groupadd devteam # 5. Add alice and bob - charlie is intentionally excluded sudo usermod -aG devteam alice sudo usermod -aG devteam bob # 6. Confirm getent group devteam # Expected: devteam:x:NNNN:alice,bob pwd && echo "Setup complete!"
su - alice (password: alice123). Return to your main user with exit. The - flag gives a full login shell so group memberships load correctly.
Session 1 · ~90 min
Learn to decode the permission string, predict access outcomes without running commands, and construct precise permission sets from scratch using both symbolic and octal notation.
Exercise 1 · ~20 min
ls -la output - no changing anything, no guessing. By the end, reading a permission string will be as fast as reading a clock.Core concept - the DAC permission model
Linux uses Discretionary Access Control (DAC). Every file has one owner and one group. Permissions are three triplets - owner, group, others - each with read (r=4), write (w=2), execute (x=1). The OS checks in order: owner first, then group, then others. First match wins - no fallback.
Run this script to create a realistic filesystem snapshot inside ~/perm_labs/ex1 with a mixture of correct and deliberately misconfigured permissions for you to audit.
# Create the directory structure mkdir -p ~/perm_labs/ex1/{webroot,config,private,.ssh,logs} cd ~/perm_labs/ex1 echo "<html>Hello world</html>" > webroot/index.html echo "user=admin&password=Tr0ub4dor" > config/db.conf echo "SECRET_KEY=abc123xyz" > config/.env echo "-----BEGIN RSA PRIVATE KEY-----" > .ssh/id_rsa echo "2024-01-15 12:00 GET /index.html 200" > logs/access.log echo '#!/bin/bash echo "Backup started"' > config/backup.sh # Set a mix of correct and misconfigured permissions chmod 644 webroot/index.html # correct for web content chmod 777 config/db.conf # PROBLEM: world-writable credentials chmod 644 config/.env # PROBLEM: world-readable secrets chmod 777 .ssh/id_rsa # PROBLEM: private key world-accessible chmod 640 logs/access.log # acceptable chmod 755 config/backup.sh # world-executable script chmod 700 private/ # correct for private dir ls -laR ~/perm_labs/ex1
Using only ls -la and stat, answer each question before revealing the answers. Build the habit of reading - not testing.
# These are your only allowed commands for this step
ls -la ~/perm_labs/ex1/
ls -la ~/perm_labs/ex1/config/
ls -la ~/perm_labs/ex1/.ssh/
stat ~/perm_labs/ex1/config/db.conf
| Question | File | Your answer |
|---|---|---|
Can a non-owner user read db.conf? | config/db.conf | write here |
Can any user modify db.conf? | config/db.conf | write here |
Which permission on id_rsa is a security problem? | .ssh/id_rsa | write here |
Can a random user read .env? | config/.env | write here |
Can a random user enter private/? | private/ | write here |
What octal value is logs/access.log? | logs/access.log | write here |
# stat shows the octal form directly stat ~/perm_labs/ex1/config/db.conf # Look for: Access: (0777/-rwxrwxrwx) # Print only octal + filename (useful in scripts) stat -c "%a %n" ~/perm_labs/ex1/config/* # Recursive audit - octal + owner + group + filename find ~/perm_labs/ex1 -exec stat -c "%a %U %G %n" {} \;
stat -c "%a" gives octal, %U owner name, %G group name, %n filename. Combine them to build audit output without parsing ls.Apply the minimum necessary permissions - do not over-restrict either.
# db.conf - credentials: owner read/write only chmod 600 ~/perm_labs/ex1/config/db.conf # .env - credentials: owner read only chmod 600 ~/perm_labs/ex1/config/.env # id_rsa - SSH refuses to use keys readable by others chmod 600 ~/perm_labs/ex1/.ssh/id_rsa # Verify all three stat -c "%a %n" ~/perm_labs/ex1/config/db.conf \ ~/perm_labs/ex1/config/.env \ ~/perm_labs/ex1/.ssh/id_rsa # Expected: 600 for all three
chmod 644 .ssh/id_rsa then try to connect - you will see UNPROTECTED PRIVATE KEY FILE and the connection is refused. This is one of the few tools that enforces permission security automatically.1. A file has permissions -rw-r--r--. Which statement is true?
2. You want to delete notes.txt inside /home/alice/docs/. The file has permissions 400. Can you delete it? What permission actually controls deletion?
/home/alice/docs/), not the file itself. Even though the file is 400, if you have write+execute on the parent you can delete it. Conversely, write permission on the file does not let you delete it without write+execute on the parent. This surprises most students because it is counter-intuitive.3. What is the octal value of -rwxr-xr--?
Exercise 2 · ~25 min
chmod u+x) and octal (chmod 755) notation, then peer-review a partner's setup to catch mistakes.Core concept - symbolic vs octal notation
Both notations set the same underlying bits. Choose the one that fits the task:
chmod 644) - sets permissions absolutely. Best when you know the exact final state. Faster for scripting.chmod u+x) - modifies relative to the current state. Best for surgical one-bit changes. Uses u (owner), g (group), o (others), a (all), with operators + (add), - (remove), = (set exactly).# Create the exercise directory
mkdir -p ~/perm_labs/ex2/{public,private,scripts,shared}
cd ~/perm_labs/ex2
touch public/index.html public/style.css
touch private/secret.key private/config.yaml
touch scripts/deploy.sh scripts/cleanup.sh
touch shared/report.txt shared/data.csv
Derive each octal value yourself before typing the command - use the builder above.
| File / Dir | Requirement | Octal |
|---|---|---|
| public/index.html | Owner: rw, Group: r, Others: r | 644 |
| private/secret.key | Owner: rw only - nobody else | 600 |
| private/config.yaml | Owner: rw, Group: r, Others: none | 640 |
| scripts/deploy.sh | Owner: rwx, Group: rx, Others: none | 750 |
| scripts/cleanup.sh | Owner: rwx only | 700 |
| shared/report.txt | Owner: rw, Group: rw, Others: r | 664 |
| shared/ (directory) | Owner: rwx, Group: rwx, Others: rx | 775 |
# Apply them chmod 644 public/index.html chmod 600 private/secret.key chmod 640 private/config.yaml chmod 750 scripts/deploy.sh chmod 700 scripts/cleanup.sh chmod 664 shared/report.txt chmod 775 shared/ # Verify all at once find ~/perm_labs/ex2 -exec stat -c "%a %n" {} \; | sort
# Add group write to shared/data.csv only chmod g+w shared/data.csv # Set config.yaml to exactly owner-read, nothing else chmod u=r,g=,o= private/config.yaml stat -c "%a %n" private/config.yaml # Expected: 400 # Add write back for owner chmod u+w private/config.yaml stat -c "%a %n" private/config.yaml # Expected: 600
chmod 600 file always sets exactly those permissions. chmod u-x file only removes owner-execute, leaving all other bits unchanged. Use octal for a known final state; use symbolic for surgical one-bit changes.If working in pairs, have your partner introduce two intentional errors in their ex2 directory. Then audit each other's work and write one sentence per problem found, describing the security implication.
# Quick audit - shows all files with octal permissions find ~/perm_labs/ex2 -exec stat -c "%a %-30n" {} \; # Flag world-writable files find ~/perm_labs/ex2 -perm -o+w -type f # Flag files executable by others that should not be find ~/perm_labs/ex2 -perm -o+x -type f
1. Which command adds execute permission for the owner only, leaving group and others unchanged?
chmod +x filechmod u+x filechmod 100 filechmod a+x file2. backup.sh has permissions 755. You want to prevent anyone except the owner from executing it. What is the correct command, and why?
chmod 700 backup.sh or chmod go-x backup.sh. The first sets absolute permissions (owner: rwx, group/others: none). The second removes execute from group and others while leaving owner unchanged. The octal form is generally preferred in scripts because it expresses the final state unambiguously regardless of what the permissions were before.Exercise 3 · ~30 min
# Create exercise directory mkdir -p ~/perm_labs/ex3 && cd ~/perm_labs/ex3 sudo touch alice_file.txt bob_file.txt shared_file.txt team_file.txt sudo chown alice:alice alice_file.txt sudo chown bob:bob bob_file.txt sudo chown alice:devteam shared_file.txt sudo chown alice:devteam team_file.txt sudo chmod 640 alice_file.txt # owner rw, group r, others none sudo chmod 600 bob_file.txt # owner rw only sudo chmod 664 shared_file.txt # owner rw, group rw, others r sudo chmod 060 team_file.txt # THE TRICK: group rw, owner has nothing! ls -la ~/perm_labs/ex3/
Fill in this table in your notes before running any commands. Work through each row by reading the permission string only.
| User | File (perms) | Can read? | Can write? | Identity used |
|---|---|---|---|---|
| alice | alice_file.txt (640) | ? | ? | ? |
| bob | alice_file.txt (640) | ? | ? | ? |
| charlie | alice_file.txt (640) | ? | ? | ? |
| alice | team_file.txt (060) | ? | ? | ? |
| bob | team_file.txt (060) | ? | ? | ? |
| charlie | team_file.txt (060) | ? | ? | ? |
# Quick test using sudo -u (no need to fully switch accounts) sudo -u alice cat ~/perm_labs/ex3/alice_file.txt 2>&1 sudo -u bob cat ~/perm_labs/ex3/alice_file.txt 2>&1 sudo -u charlie cat ~/perm_labs/ex3/alice_file.txt 2>&1 # The trick case - predict alice's result before running! sudo -u alice cat ~/perm_labs/ex3/team_file.txt 2>&1 # Expected: PERMISSION DENIED - alice is owner but owner bits are 0! sudo -u bob cat ~/perm_labs/ex3/team_file.txt 2>&1 # Expected: success - bob is in devteam, group bits are rw sudo -u charlie cat ~/perm_labs/ex3/team_file.txt 2>&1 # Expected: DENIED - charlie is others, others bits are 0
team_file.txt has permissions 060. Alice is the owner but owner bits are 0 - so Alice is denied. Bob is in devteam, group bits are 6 - Bob can read and write. Linux never falls through: first match wins, always. The owner can be locked out of their own file.1. A file has permissions 070 and is owned by alice:devteam. Alice tries to read the file. What happens?
2. Why does root bypass permission checks? Is there any way to protect sensitive data from root?
Session 2 · ~100 min
Apply what you learned to real-world scenarios: diagnose a broken web server using only permission fixes, configure a shared team directory with setgid and sticky bit, hunt for misconfigurations with find, and control default permissions with umask.
Exercise 4 · ~25 min
Core concept - web server permissions
A web server process runs as a low-privilege user (www-data on Debian/Kali). For it to serve a file it must:
# Create directory structure mkdir -p ~/perm_labs/ex4/{webroot/{html,uploads},config,logs} cd ~/perm_labs/ex4 echo "<html><body><h1>Welcome</h1></body></html>" > webroot/html/index.html echo "DB_HOST=localhost DB_PASS=secretpass123" > config/app.conf echo "upload content" > webroot/uploads/photo.jpg touch logs/access.log logs/error.log # Deliberately break four things chmod 700 webroot/html/ # Problem 1: not traversable by server process chmod 644 config/app.conf # Problem 2: world-readable credentials chmod 555 webroot/uploads/ # Problem 3: uploads dir not writable chmod 444 logs/access.log # Problem 4: log not writable ls -la ~/perm_labs/ex4/webroot/ ls -la ~/perm_labs/ex4/config/ ls -la ~/perm_labs/ex4/logs/
# Simulate the server process as a less-privileged user (bob) sudo -u bob cat ~/perm_labs/ex4/webroot/html/index.html # Expected: Permission denied - html/ is 700 (owner only) sudo -u bob cat ~/perm_labs/ex4/config/app.conf # This SUCCEEDS - but should it? World-readable credentials is the problem! sudo -u bob sh -c "echo 'upload' > ~/perm_labs/ex4/webroot/uploads/new.txt" # Expected: Permission denied - uploads/ is 555 (no write) sudo -u bob sh -c "echo 'log entry' >> ~/perm_labs/ex4/logs/access.log" # Expected: Permission denied - log is 444 (read-only)
| Problem | Current | Correct perm | Security risk if too open |
|---|---|---|---|
| html/ not traversable | 700 | write here | write here |
| app.conf world-readable | 644 | write here | write here |
| uploads/ not writable | 555 | write here | write here |
| access.log not writable | 444 | write here | write here |
# Fix 1: html/ needs to be traversable and readable by the server process chmod 755 ~/perm_labs/ex4/webroot/html/ # 755: owner rwx, group rx, others rx - server can enter and read # Not 777: the server never needs to write to the web root # Fix 2: app.conf should NOT be world-readable chmod 640 ~/perm_labs/ex4/config/app.conf # Owner rw, group r (server group can read), others denied # Fix 3: uploads/ needs to be writable by the server chmod 755 ~/perm_labs/ex4/webroot/uploads/ # Fix 4: log file needs to be writable by the server process chmod 644 ~/perm_labs/ex4/logs/access.log # Re-test sudo -u bob cat ~/perm_labs/ex4/webroot/html/index.html # now works sudo -u bob cat ~/perm_labs/ex4/config/app.conf # still denied - correct! echo "All fixes verified"
1. A file has correct content but the web server returns 403 Forbidden. The file itself is 644. What else should you check?
ls -la on each parent and look for the x bit in the others or group position.2. Why is a world-writable uploads directory dangerous even if the web server only needs to write there?
chown + 700 or 750.Exercise 5 · ~30 min
# Create the shared project directory sudo mkdir -p /opt/project sudo chown root:devteam /opt/project sudo chmod 770 /opt/project # Alice creates a file sudo -u alice sh -c "echo 'Alice data' > /opt/project/alice_notes.txt" # Check the group on the new file ls -la /opt/project/alice_notes.txt # Group = alice (her primary group) - NOT devteam! # Bob cannot read it because he is not in alice's primary group sudo -u bob cat /opt/project/alice_notes.txt # Likely: Permission denied
# Add setgid - new files will inherit devteam group sudo chmod g+s /opt/project # Equivalent: sudo chmod 2770 /opt/project ls -la /opt/ # Look for 's' in group-execute position: drwxrws--- # Alice creates another file sudo -u alice sh -c "echo 'Alice v2' > /opt/project/alice_v2.txt" ls -la /opt/project/alice_v2.txt # Group should now be devteam automatically sudo -u bob cat /opt/project/alice_v2.txt # Expected: success - group is devteam, bob is in devteam
# WITHOUT sticky bit: bob can delete alice's files sudo -u bob rm /opt/project/alice_v2.txt # SUCCEEDS - bob has write+execute on the directory, so he can delete. Bad! # Recreate alice's file sudo -u alice sh -c "echo 'Recreated' > /opt/project/alice_v2.txt" # Add the sticky bit sudo chmod +t /opt/project # Or: sudo chmod 3770 /opt/project (2=setgid + 1=sticky = 3) ls -la /opt/ # Look for 'T' or 't' in the others-execute position # Capital T = sticky set, others have no execute # Lowercase t = sticky set and others have execute # Now bob tries to delete alice's file sudo -u bob rm /opt/project/alice_v2.txt # Expected: Operation not permitted - sticky bit protects it # But bob CAN still delete his own files sudo -u bob sh -c "echo 'Bob data' > /opt/project/bob_notes.txt" sudo -u bob rm /opt/project/bob_notes.txt # Expected: success - sticky only prevents deleting OTHER people's files
chown root:devteam /opt/project + chmod 3770 /opt/project. The 3 combines setgid (2) + sticky (1). New files inherit devteam group automatically. Members cannot delete each other's work.1. What does the s in the group-execute position mean in drwxrws---?
2. The sticky bit is set on /opt/project. Bob is in devteam and has write+execute on the directory. Can Bob delete Alice's file? Can Bob delete his own file?
/tmp works on any Linux system: world-writable so anyone can create temp files, but the sticky bit prevents users from deleting each other's files.Exercise 6 · ~25 min
find to hunt for dangerous permissions automatically. You will plant deliberate misconfigurations, then build and run the same find commands that penetration testers and security auditors use in real engagements.# Create a directory tree with deliberate problems to find mkdir -p ~/perm_labs/ex6/{system,home/alice,scripts,tmp_share} # World-writable files - critical echo "server config" > ~/perm_labs/ex6/system/server.conf chmod 777 ~/perm_labs/ex6/system/server.conf echo "cron task" > ~/perm_labs/ex6/scripts/nightly.sh chmod 777 ~/perm_labs/ex6/scripts/nightly.sh # Private key with bad permissions - high echo "-----BEGIN RSA PRIVATE KEY-----" > ~/perm_labs/ex6/home/alice/.ssh_key chmod 644 ~/perm_labs/ex6/home/alice/.ssh_key # Setuid binary simulation cp /bin/ls ~/perm_labs/ex6/scripts/myls chmod 4755 ~/perm_labs/ex6/scripts/myls # Setgid binary simulation cp /bin/ls ~/perm_labs/ex6/scripts/myls2 chmod 2755 ~/perm_labs/ex6/scripts/myls2 # World-writable directory chmod 777 ~/perm_labs/ex6/tmp_share/ echo "Misconfigurations planted. Now find them."
# Find world-writable FILES in the exercise directory find ~/perm_labs/ex6 -type f -perm -0002 # Find world-writable DIRECTORIES find ~/perm_labs/ex6 -type d -perm -0002 # Show with full details find ~/perm_labs/ex6 -perm -0002 -exec ls -la {} \; # Real-world version (whole system, staying on one filesystem) find / -xdev -type f -perm -0002 2>/dev/null
chmod 4777 /bin/bash - next time cron runs, any user can get a root shell with bash -p.# Find setuid binaries find ~/perm_labs/ex6 -type f -perm -4000 # Find setgid binaries find ~/perm_labs/ex6 -type f -perm -2000 # Find BOTH in one command find ~/perm_labs/ex6 -type f \( -perm -4000 -o -perm -2000 \) # Show with owner, group, octal permissions find ~/perm_labs/ex6 \( -perm -4000 -o -perm -2000 \) \ -exec stat -c "%a %U %G %n" {} \; # Real-world: all setuid binaries on the system find / -xdev -type f -perm -4000 2>/dev/null
passwd, su, sudo, mount, ping. Any binary not on the expected list is suspicious. Run strings on it to see what commands it calls - PATH hijacking is the first thing to check.# Create a file owned by a UID with no account sudo touch ~/perm_labs/ex6/orphaned_file.txt sudo chown 9998:9998 ~/perm_labs/ex6/orphaned_file.txt 2>/dev/null || true # ls will show numeric UID 9998 instead of a username # Find files with no matching user find ~/perm_labs/ex6 -nouser 2>/dev/null # Find files with no matching group find ~/perm_labs/ex6 -nogroup 2>/dev/null # Real world: search entire filesystem find / -xdev \( -nouser -o -nogroup \) 2>/dev/null
Combine all checks into a single script. Save it as ~/perm_labs/audit.sh - this is the skeleton of a real permission audit tool.
# Save as ~/perm_labs/audit.sh then run: bash ~/perm_labs/audit.sh ~/perm_labs/ex6
cat > ~/perm_labs/audit.sh << 'SCRIPT'
#!/bin/bash
TARGET="${1:-$HOME/perm_labs/ex6}"
echo "=== Permission Audit: $TARGET ==="
echo -e "\n[CRITICAL] World-writable files:"
find "$TARGET" -xdev -type f -perm -0002 2>/dev/null | \
while read f; do echo " $(stat -c '%a %U %G %n' "$f")"; done
echo -e "\n[CRITICAL] World-writable directories:"
find "$TARGET" -xdev -type d -perm -0002 2>/dev/null | \
while read f; do echo " $(stat -c '%a %U %G %n' "$f")"; done
echo -e "\n[HIGH] Setuid binaries:"
find "$TARGET" -xdev -type f -perm -4000 2>/dev/null | \
while read f; do echo " $(stat -c '%a %U %G %n' "$f")"; done
echo -e "\n[MEDIUM] Setgid binaries:"
find "$TARGET" -xdev -type f -perm -2000 2>/dev/null | \
while read f; do echo " $(stat -c '%a %U %G %n' "$f")"; done
echo -e "\n[INFO] Orphaned files:"
find "$TARGET" -xdev \( -nouser -o -nogroup \) 2>/dev/null | \
while read f; do echo " $f"; done
echo -e "\n=== Audit complete ==="
SCRIPT
chmod +x ~/perm_labs/audit.sh
bash ~/perm_labs/audit.sh ~/perm_labs/ex6
1. What does find / -perm -4000 -type f search for?
2. You find a world-writable shell script at /etc/cron.daily/cleanup.sh owned by root. Rate the severity and explain the exact attack path.
/etc/cron.daily/ as root. Because cleanup.sh is world-writable, any user can append a command - for example: echo 'chmod 4777 /bin/bash' >> /etc/cron.daily/cleanup.sh. When cron next runs the script as root, /bin/bash gets the setuid bit. The attacker then runs bash -p to get a root shell. This is a textbook local privilege escalation via cron + world-writable script.Exercise 7 · ~20 min
umask, a value that masks out (removes) bits from the theoretical maximum. Understanding umask is essential: it explains why files created on a developer's workstation might have different defaults than those created by a system service - and why that difference can become a security problem.Core concept - how umask works
umask is a bitmask subtracted from the default maximum. Files default to 666 (no execute - you should never create executable files by default), directories default to 777. The umask removes specific bits: umask 022 removes group-write and others-write, giving files 644 and directories 755.
umask 027 - lasts until the shell exitsumask 027 to ~/.bashrc (per-user) or /etc/profile (system-wide)# View current umask in octal umask # On Kali, default is usually 0022 # View in symbolic form (shows what IS allowed, not what is removed) umask -S # Example: u=rwx,g=rx,o=rx # Create a file and directory with the current umask mkdir -p ~/perm_labs/ex7 && cd ~/perm_labs/ex7 touch test_default.txt mkdir test_default_dir ls -la # With umask 022: file = 644, dir = 755
# Switch to more restrictive umask for this session only umask 027 # Create new files touch test_027.txt mkdir test_027_dir # Compare side by side stat -c "%a %n" test_default.txt test_027.txt # test_default.txt = 644 ; test_027.txt = 640 stat -c "%a %n" test_default_dir test_027_dir # test_default_dir = 755 ; test_027_dir = 750
# Maximum privacy - nothing shared with anyone umask 077 touch test_077.txt mkdir test_077_dir stat -c "%a %n" test_077.txt # Expected: 600 stat -c "%a %n" test_077_dir # Expected: 700 # This is appropriate for: private keys, personal secrets # Too restrictive for: shared servers, team directories
# Add to ~/.bashrc for this user permanently echo "umask 027" >> ~/.bashrc # Test in a new subshell bash -c "umask" # Expected: 0027 # Check system-wide default grep -i umask /etc/login.defs # UMASK line controls the system-wide default for new user accounts grep umask /etc/profile 2>/dev/null || echo "Not set in /etc/profile" # Reset back to 022 for this session to avoid affecting other exercises umask 022
umask 027 in /etc/profile - all users and services default to group-read, others-denied. For a service account (web server, database): set umask 027 or 077 in the service's startup script so its config files are never world-readable by default.1. What permissions will a new file have if the current umask is 027?
2. A developer sets umask 000 on a shared server to "avoid permission issues". What is the security problem?
umask 000 means no bits are masked out. Files are created with 666 (world-readable and writable) and directories with 777. Every file the developer creates - config files, scripts, data - is immediately readable and writable by all users on the system. An attacker with any compromised account can read credentials, modify configs, or overwrite scripts. The correct fix for "permission issues" is to identify the actual requirement and set it precisely - not to disable all protection.3. You set umask 027 in your terminal but the files created by a cron job still come out as 644. Why?
/etc/profile or the cron daemon's own startup config. Your terminal's umask does not affect cron. To change the umask for cron jobs, add umask 027 at the top of the crontab file or inside the script itself.Both sessions complete
chmod 644) and symbolic (chmod u+x) notation, and understood when each approach is appropriatefind -perm, -nouser, and -xdev to hunt for world-writable files, setuid/setgid binaries, and orphaned files - the same commands used in real penetration tests and security auditsReady for Session 3? Next topics include setuid binary exploitation, credential file exposure and remediation, Linux ACLs for per-user fine-grained control, and building a full automated permission audit and reporting tool.