HomeTopicsPermissions › Auditing

Auditing Who Can Touch What on Your File Servers

Permissions are set once and questioned rarely. A partner is onboarded, a folder is shared, a service account is granted access for a project — and then everyone moves on. The grants stay. Months later, nobody can say with confidence who can read the finance drop, whether the departed partner's account still works, or why a service account has write access to a folder its job no longer touches. That slow accumulation has a name: permission drift, and it is the natural state of any server nobody audits.

An audit is simply the discipline of asking, on purpose and on a schedule, the question the server answers implicitly on every request: who can touch what, and should they? You do not need a product to do it — the operating system already knows every answer, and a handful of built-in commands will pull them out. What you need is a method for reading effective access rather than what the configuration screen claims, a way to find grants that have outlived their reason, and a cadence that catches drift before it becomes an incident.

This article is that method, tooling-light and cross-platform. It is part of our Permissions series and is where the earlier articles get verified: the isolation, the least-privilege grants, and the upload inheritance are only real if an audit confirms they still hold.

Granted Is Not Effective

The single most important idea in auditing permissions is that the grant you can see is not the access the user gets. What a user can actually do — their effective permission — is the sum of several layers, and any one of them can change the answer:

  • Group memberships. A user gets the access of every group they belong to, and those memberships are set somewhere other than the folder. An account can reach a folder through a group you were not thinking about.
  • Inheritance. A file's permissions may come from a parent folder rather than from anything set on the file itself, so the file "has" access nobody granted it directly.
  • Deny and masks. On Windows an explicit deny overrides an allow; on Linux the ACL mask can silently cap a named grant below what it appears to say. The listed grant and the working grant differ.
  • Traverse. A user may hold read on a file but be unable to reach it because a directory somewhere in the path denies them traverse — access granted in name, blocked in practice.

A concrete example makes the gap vivid. Suppose a folder's ACL grants the group reporting read access and nothing else — a tidy, limited-looking grant. But a service login you would never expect on that folder, one that happens to belong to reporting through an unrelated project, now reads it too. An administrator scanning the folder's entries would never see that account, because it is not named there; its membership, set somewhere else entirely, is what pulled it into scope. The grant on the folder did not change. What the grant means did. Effective-access thinking is what surfaces the invisible member.

Because of these layers, an audit that only reads the ACEs on a folder is guessing. A real audit reads effective access — ideally by acting as the account, which is the one method no layer can fool. The commands below are organized around that principle.

Remember: reading the grant tells you what someone wrote down; testing as the account tells you what the server will actually do. When those disagree — and group memberships, inheritance, deny entries, and masks make them disagree often — the test is the truth and the grant is the story.

Reading Effective Permissions on Linux

Three moves answer most questions: identify the account and its groups, resolve the path to see every gate along it, and then act as the account to confirm. Start with identity and path:

    # who is this account, and which groups pull it into scope?
$ id partnera
uid=1105(partnera) gid=1105(partnera) groups=1105(partnera),2001(fileadmin)

    # resolve every component of a path -- the fast way to see WHY access works or not
$ namei -l /srv/sftp/partnera/out/pricelist.csv
 drwxr-xr-x root      root     /
 drwxr-xr-x root      root     srv
 drwxr-xr-x root      root     sftp
 drwxr-xr-x root      root     partnera
 drwxr-s--- fileadmin partnera out
 -rw-r----- fileadmin partnera pricelist.csv

The namei -l output is the diagnostic that ends most "why can't they read it" arguments: it lays out the owner, group, and mode of every directory from the root down, so a missing traverse bit three levels up is visible at a glance. Once you can see the path, confirm the effective access by becoming the account and both doing what it should and attempting what it should not:

    # act as the account -- the test no layer can fool
$ sudo -u partnera ls -l /srv/sftp/partnera/out          # should succeed (read)
$ sudo -u partnera touch /srv/sftp/partnera/out/probe     # should FAIL (read-only)
$ sudo -u partnera cat  /srv/sftp/partnerb/out/anything   # should FAIL (isolation)

    # dump the full ACLs across a partner tree for the record
$ getfacl -R /srv/sftp/partnera

    # sweep for over-permissive files across the whole area
$ sudo find /srv/sftp -perm -0002 -print        # anything world-writable
$ sudo find /srv/sftp -perm -0004 -type f -print # data readable by "other"

Those two find sweeps are worth running regularly on their own. World-writable files and directories are a standing risk on a multi-party server, and world-readable data files in a partner tree are the "wide-open upload" from the inheritance article showing up after the fact. Finding them is a one-line command; the only trick is remembering to look.

Reading Effective Permissions on Windows

Windows keeps the same shape — identify the account, dump the ACLs, confirm effective access — with its own tools. The built-in icacls dumps a whole tree, and PowerShell's Get-Acl reads a single object cleanly:

    REM group membership for the account
C:\> net user partnera
C:\> net localgroup

    REM dump ACLs for a whole tree to a file for the record
C:\> icacls D:\ftproot\partnera /T /C

    REM read one object's ACL, formatted
PS> Get-Acl D:\ftproot\partnera\out | Format-List

For true effective access — the Windows answer to sudo -u — you have two good options. The graphical one is built in: open the folder's Properties, then Security, Advanced, and the Effective Access tab; pick an account and Windows computes exactly what it gets after every group, deny, and inheritance rule is applied. For a scriptable version, the free AccessChk utility reports effective access from the command line, which is handy for sweeping many folders at once. Either way, the goal is the same as on Linux: compute what the account really gets rather than eyeballing the ACE list. On a Windows transfer server, cross-checking those results against the service's own configured user list closes the loop — Sysax Multi Server keeps per-user authentication, so the accounts you audit on the filesystem line up with the accounts the service will actually let in.

Hunting Orphaned Grants

The highest-value find in any permission audit is the orphan: a grant that has outlived the account or the reason it was created. Orphans are pure risk — they grant access to something no longer accountable to anyone — and they are easy to spot once you know the tells.

On Linux, the tell is a file owned by a numeric ID with no matching account, or an ACL entry naming a user who no longer exists. Both show up as bare numbers where a name should be:

    # files whose owner or group no longer maps to any account
$ sudo find /srv/sftp -nouser -o -nogroup

    # ACL entries for users/groups that no longer resolve show as raw numbers
$ getfacl -R /srv/sftp/oldpartner | grep -E ':[0-9]+:'

On Windows, a deleted account leaves its security identifier behind in any ACL that referenced it. Where a name would normally appear, icacls prints the raw SID — a string like S-1-5-21-... — and that is your flag that an account was removed while its grant was not:

C:\> icacls D:\ftproot\partnera
D:\ftproot\partnera FILESRV\partnera:(OI)(CI)(RX)
                    S-1-5-21-1004336348-1177238915-682003330-1174:(OI)(CI)(M)
                                    ^ unresolved SID = deleted account, grant remains

Both cases resolve the same way: confirm the account is genuinely gone, then remove the grant (setfacl -x or icacls /remove) and, if the files are orphaned too, reassign or archive them. The subtler orphan is the account that still exists but should not — the partner whose contract ended, the service account for a decommissioned job. Those do not show up as numbers; you catch them only by reconciling the live account list against the list of relationships that are still real, which is why the audit needs a business input, not just a command.

Audit the Service's View, Too

A transfer server has a second permission model the filesystem audit will completely miss: the service's own user database. As the permission models article explained, many FTP and SFTP servers keep their own logins, home directories, and rights that are separate from operating-system accounts. A file owned by a numeric ID tells you nothing about a virtual user that lives only inside the service — and a virtual user pointed at a stale root tells you nothing the filesystem can see.

So alongside the getfacl and icacls passes, enumerate the service's configured accounts and, for each, confirm three things: the account still maps to a real, current relationship; its configured root points where you expect; and the operations it is permitted match its job. Then reconcile the two lists in both directions. A service account with no filesystem footprint may be a harmless leftover or a login waiting to be abused; a partner folder with no matching service account is very likely the orphan your filesystem check flagged from the other side. The audit is only complete when the service's view and the filesystem's view agree — a disagreement is precisely where a forgotten door tends to hide.

A Tooling-Light Method: Snapshot and Diff

You do not need to eyeball a whole server every time. The most sustainable audit for a small team is a snapshot-and-diff: dump the permissions to a text file, compare against the previous snapshot, and read only what changed. Drift is, by definition, the difference between two points in time, so a diff surfaces it precisely and ignores everything that stayed correct.

    # Linux: snapshot, compare to last time, then promote the new snapshot
$ getfacl -R /srv/sftp > acl-current.txt
$ diff acl-previous.txt acl-current.txt      # every line of drift since last review
$ mv acl-current.txt acl-previous.txt

    # Windows: same idea with icacls and fc
C:\> icacls D:\ftproot\* /T /C > acl-current.txt
C:\> fc acl-previous.txt acl-current.txt

Keep the snapshots somewhere the audited server cannot casually overwrite, and pair them with a plain inventory you maintain by hand — one row per account: the login, its root, the rights it should have, the business owner who vouches for it, and when it was last reviewed. The commands tell you what is; the inventory tells you what should be; the audit is reconciling the two. That reconciliation is also the natural companion to log review, since the same accounts appear in both — our transfer logging and audit series covers the activity side.

When a review turns up something wrong, write it down as a finding rather than fixing it silently: what was found, which account and path, what you changed, and when. A short remediation list turns an audit from a private tidy-up into a record you can show — valuable when someone later asks whether a given exposure was known and closed. It also stops a quick fix from becoming an unlogged change that the next audit flags all over again, wondering who did it and why.

A Cadence That Keeps Drift in Check

An audit run once is a snapshot; an audit run on a rhythm is a control. Three triggers keep drift from accumulating faster than you remove it:

  • On a schedule. A full review every quarter is a sensible default for most transfer servers — frequent enough that drift stays small, rare enough that it does not become a chore people skip. The snapshot-and-diff keeps each run short.
  • On offboarding. The moment a partner relationship ends or a job is retired, remove the account and its grants and roots together. Offboarding is where orphans are born; a small checklist item here prevents the expensive find later.
  • On change. Any time you add an account, open a folder, or wire up a new flow, run the effective-access test as part of the change. Auditing at the moment of change is cheaper than discovering the drift a quarter later.

The habit to build is that access is never granted without a review date attached and never removed later than the reason for it. Most drift is not malicious or even careless; it is simply access that was correct once and was never revisited when the world moved on.

If a full-server review feels too big to sustain, scope by risk rather than skipping it. Audit the internet-facing accounts, the ones with write access, and the folders holding sensitive data first and most often; the low-risk read-only corners can wait for the scheduled pass. An audit you actually run on the risky tenth of the server beats a comprehensive one you keep postponing until it never happens.

A Five-Minute Audit of One Account

When you need to check a single account rather than the whole server — a newly onboarded partner, or one whose behavior looks off — run this short sequence and read each result against what the account's job is supposed to allow:

  1. Identify. Run id (or net user) and confirm the group memberships are exactly the ones you expect, with nothing extra pulling the account into scope.
  2. Resolve the path. Run namei -l to the account's root and check that every gate along the way is deliberate — no surprise traverse, no missing one.
  3. Act as the account. Do the job (it should work), then attempt the forbidden (it should fail): read, then a cross-partner read, then a write into a read-only area, then a delete.
  4. Check the born-with permission. Upload a probe file and inspect what it inherits, the test from the inheritance article.
  5. Confirm in the service. Make sure the account still maps to a live relationship and points at the correct root.
  6. Record it. Note the result and the review date in your inventory, so the next audit starts from a known baseline.

Six steps, one account, a few minutes — and you have replaced "I think that's fine" with "I checked, and here is what I found." Done at onboarding, it also means no account ever goes live unverified.

The Audit at a Glance

Question Linux Windows
Who is this account and its groups? id, getent group net user, net localgroup
What does it effectively get on a path? sudo -u test; namei -l Effective Access tab; AccessChk
Full permissions across a tree? getfacl -R icacls /T
Anything over-permissive? find -perm -0002 / -0004 Search ACLs for Everyone / Users
Any orphaned grants? find -nouser -nogroup; numeric ACL entries Unresolved S-1-5-... SIDs in icacls
What changed since last time? diff of getfacl snapshots fc of icacls snapshots

Where This Leaves You

Auditing permissions is not a product you buy; it is a habit you keep. Read effective access rather than the grant on the folder, and confirm it by acting as the account. Hunt orphans — numeric owners and unresolved SIDs — and reconcile the live account list against the relationships that are still real. Make the routine sustainable with snapshot-and-diff so each review reads only what drifted, and put it on a cadence of schedule, offboarding, and change so drift never outruns you.

The natural next step is the blast radius of one stolen credential, which takes a single audited account and asks how far it reaches — the offensive-minded flip side of this defensive review. Auditing also feeds directly into threat modeling and the broader tightening in hardening transfer servers. And if an audit turns up over-permissive grants, the fix is the least-privilege work applied again.

Frequently Asked Questions

What is the difference between granted and effective permissions?
A granted permission is the entry written on a folder or file; an effective permission is what a user actually gets after group memberships, inheritance, deny entries, and masks are all applied. They often differ, which is why reading the ACL alone can mislead you. The reliable way to know the effective answer is to test as the account or use an effective-access tool.
How do I test what a specific account can really do?
On Linux, act as the account with sudo -u account and try both what it should be able to do and what it should not. On Windows, use the Effective Access tab under a folder's Advanced security settings, or the AccessChk command-line tool. Acting as the account is the one test that no permission layer can fool.
What is an orphaned permission grant?
It is a grant that outlived the account or the reason it existed — access left behind after a user was deleted or a project ended. On Linux it shows as a numeric owner or ACL entry with no matching name; on Windows it appears as an unresolved SID (S-1-5-...) in the ACL. Orphans are pure risk and should be removed as soon as they are confirmed.
How often should I audit file server permissions?
A full review each quarter suits most transfer servers, backed by two event triggers: audit at offboarding, when a partner or job goes away, and audit at change, whenever you add an account or open a folder. The scheduled pass catches slow drift; the event triggers catch it at the moment it is created, which is cheaper.
Do I need special software to audit permissions?
No. The operating system already knows every answer, and built-in commands — id, getfacl, namei, and find on Linux, net user, icacls, Get-Acl, and the Effective Access tab on Windows — pull them out. A snapshot-and-diff of the ACLs gives you drift detection with nothing more than a scheduled command and a saved text file.

From the Sysax team: we build secure file transfer software for Windows — Sysax Multi Server, an FTP, FTPS, SFTP, and HTTPS server, and Sysax FTP Automation for scheduled, scripted transfers. Free trials are on the download page.