Why it matters

Enterprise clusters have hundreds or thousands of users organized into overlapping groups. A finance analyst might belong to finance, all-analysts, and audit-readers simultaneously, and a file might need to grant slightly different permissions to each. The POSIX model with only three roles cannot express this; extended ACLs and Ranger together can.

Regulatory compliance drives many of these requirements. Data classification schemes, GDPR requirements, HIPAA controls, and PCI-DSS all mandate specific access control patterns that need to be provably enforced. A well-configured Ranger deployment produces the audit trail that regulators expect.

Advertisement

The architecture

Every HDFS inode has three fields for permission control: owner (a username), group (a group name), and a nine-bit mode string like rwxr-x--- controlling how the owner, group members, and others can interact with the file. This is straight POSIX semantics and works exactly like a Unix filesystem for reads, writes, execute, and directory traversal.

Extended ACLs augment this with a variable-length list of additional access control entries. Each entry names either a specific user or a specific group and specifies the permission bits granted to that identity. When a client tries to access a file, the NameNode checks the owner match first, then the group match, then walks the extended ACL, and finally falls back to the other bits.

HDFS file inode with owner, group, and mode bitsPOSIX moderwxr-xr-- styleExtended ACLsadditional user/group entriesRanger policiespath-based enforcementNameNode checks all three on every open/read/write RPC; deny wins
Three layers of access control: POSIX mode bits, extended ACLs, and Ranger policy. Which of them is consulted, in what order, and which one is allowed to win is the subject of the rest of this article.
Advertisement

How it works end to end

Three subsystems decide whether Alice may read /data/finance/ledger.parquet, and they do not compose the way most people assume. Kerberos, or its absence, establishes who Alice is. The NameNode's group mapping establishes what she is a member of. Then exactly one authorization engine runs: either the built-in check over mode bits and ACLs, or - when an INodeAttributeProvider such as the Ranger plugin is installed - that provider's enforcer, which may consult the built-in check as a fallback or may ignore it entirely.

That last point overturns the intuition the diagram above invites. A Ranger policy is not an extra filter narrowing what the mode bits already permit. On HDFS it substitutes for the native check, so a Ranger allow rule grants access to a path whose mode string reads rwx------ and whose owner is somebody else. Whether the native check runs at all afterwards is one boolean in the plugin's configuration. Get that boolean wrong in one direction and every setfacl your team has ever run stops mattering; get it wrong in the other and the cluster denies everything the moment the plugin loads.

The rest of this article walks the chain in order: identity, group resolution, mode bits, extended ACLs, the mask, the check algorithm itself, the three escape hatches (superuser, supergroup, proxy users), the two features that quietly carry old permissions forward (trash and snapshots), and finally the external policy engines and how to audit any of it.

Where Kerberos stops and authorization begins

Authorization consumes two inputs: a username and a group list. Both reach the NameNode by mechanisms that are easy to overlook, and one of them is not a security mechanism at all when hadoop.security.authentication is left at simple.

Under simple authentication the Hadoop RPC handshake carries a username the client picked. The client library derives it from the OS login, and it prefers the HADOOP_USER_NAME environment variable over that when the variable is set. Nothing verifies it. A laptop holding the cluster's configuration files and able to reach the NameNode port can export HADOOP_USER_NAME=hdfs and be the superuser. WebHDFS is blunter still: an unauthenticated REST call takes its identity from a user.name query parameter, which is a string in a URL that anybody can type.

So on a simple-auth cluster the permission model is documentation with an enforcement mechanism attached. It still earns its keep - a batch job that would have removed the wrong directory gets a denial, and accidents are most of what actually happens - but it stops nothing deliberate. Every claim in this article about who can read what is conditional on hadoop.security.authentication being kerberos. Establishing a verified principal and mapping it down to a plain username through the auth_to_local rules is the subject of Hadoop Kerberos architecture; this article picks up one step later, with a username the NameNode is entitled to believe.

The other input, group membership, never travels with the request at all, and that is where the surprising denials come from.

Nine bits that look like POSIX and are not

Every inode carries an owner string, a group string, and nine mode bits. hdfs dfs -ls prints them in the familiar -rw-r----- shape, and chmod, chown, and chgrp behave the way muscle memory expects. Underneath, the semantics diverge from a real POSIX filesystem in four ways, each of which produces a recognisable class of confusion.

The execute bit on a file means nothing. HDFS never runs anything - there is no exec path, no interpreter lookup, no shebang handling. The bit is stored and reported faithfully and is never consulted by any check. A pipeline that copies a shell script into HDFS and carefully preserves mode 0755 is preserving a decoration.

The execute bit on a directory means traversal, and only traversal. It does not imply the right to list the directory - read grants listing, execute grants the right to name a child inside a longer path. That split is the single most common source of denials that look impossible. A user holding rwx on /data/finance/reports is still refused a file inside it when the execute bit is missing for them somewhere above, on /data or /data/finance. The traversal fails before the target inode's own permissions are ever examined, and the error names the file the user asked for.

There is no setuid and no setgid. Both bits can be set and are displayed; neither does anything, because there is no process here to run under a borrowed identity. What POSIX uses setgid on directories for, HDFS simply does unconditionally: a newly created file or directory takes the group of its parent, always, with no way to disable it. Group ownership therefore flows down a tree as the tree grows, which is why chgrp -R is normally a one-time repair rather than a recurring chore.

The creation mask is a configuration property, not a shell setting. fs.permissions.umask-mode, default 022, is applied by the client at create time. It lives in client configuration, so two gateway hosts with different values write differently-permissioned files into the same directory, and the pattern of which files are wrong tracks which machine the job ran on rather than anything about the data.

Groups are resolved on the NameNode, not on your laptop

The client sends a username. It does not send a group list, and the NameNode would not trust one if it did - a client-supplied group list would make every ACL in the cluster self-service. Membership is resolved server side, inside the NameNode process, by the plugin named in hadoop.security.group.mapping.

The default, org.apache.hadoop.security.JniBasedUnixGroupsMappingWithFallback, asks the operating system of the NameNode host: through native calls where the JNI library is present, otherwise by shelling out. That one sentence accounts for a large share of confused access tickets. The groups that decide your access are the groups the NameNode host can see. If your users live in a corporate directory and the NameNode host is joined to it, the two views agree. If that host is not joined, or the account simply does not exist in its user database, the lookup returns an empty list - the user matches no group entry anywhere in the cluster and every check falls straight through to the other bits. Nothing logs an error, because an empty group list is a perfectly valid answer.

org.apache.hadoop.security.LdapGroupsMapping takes the OS out of the path and queries the directory directly using its own bind credentials, search base, and filters. It trades one dependency for another: the NameNode now performs a directory round trip on a code path that runs during authorization, so a slow directory becomes NameNode latency and an unreachable one becomes a cluster-wide access failure. CompositeGroupsMapping combines providers, which is the usual way to keep a handful of service accounts resolving locally while human users come from LDAP.

Because these lookups are expensive they are cached. hadoop.security.groups.cache.secs defaults to 300 seconds and hadoop.security.groups.negative-cache.secs to 30. The operational consequence runs in both directions: adding somebody to a group may take five minutes to take effect, and - far more important during an incident - removing them takes just as long. If you are revoking access under pressure, run hdfs dfsadmin -refreshUserToGroupsMappings and do not assume the directory change alone did anything yet.

Two commands end every argument about group membership. hdfs groups alice asks the NameNode what it believes, which is the only opinion that decides anything. Comparing that against id -Gn alice on the user's own machine is how you discover the two disagree, usually within seconds. For the small set of accounts that should never depend on any directory being reachable, hadoop.user.group.static.mapping.overrides pins the answer in configuration.

Extended ACLs - access entries and default entries

Three roles cannot express "finance may write, audit may read, the ingest service may read, nobody else sees anything". Extended ACLs add a variable-length list of entries to an inode, each naming one specific user or one specific group along with the bits it grants. They are gated by dfs.namenode.acls.enabled, on by default on Hadoop 3 and something you had to switch on deliberately on Hadoop 2 - which is why old clusters carry ACL commands in runbooks that silently never worked.

An inode can carry two distinct ACLs, and confusing them is the most common mistake in this area after the mask.

The access ACL is the one that is evaluated. It exists on files and directories, and it answers the question "may this user do this now".

The default ACL exists only on directories and is never evaluated for access at all. It is a template. When a child is created inside that directory, the default ACL is copied onto the child as the child's access ACL - and, if the child is itself a directory, as its default ACL too, so the template keeps propagating down the tree as the tree grows. Hadoop 3 also exposes dfs.namenode.posix.acl.inheritance.enabled, which governs whether that copy follows the POSIX rules faithfully rather than the earlier, subtly different Hadoop behaviour.

Copied at creation time. That is both the entire point and the entire trap. Editing a directory's default ACL changes nothing about the files already inside it; each of those received its own copy when it was written and holds it independently forever afterwards. Teams routinely add a default ACL to a landing zone that already holds two years of data, verify it with getfacl on the directory, sign off the change, and then spend a fortnight wondering why the new team can read this month and nothing before it. The remedy is a separate recursive pass that sets access entries on the existing children, and it is a different command from the one that set the template.

# access entries on a tree that already holds data - applies to what exists now
hdfs dfs -setfacl -R -m user:svc_ingest:r-x /data/finance

# a default entry - a template, applied only to children created from now on
hdfs dfs -setfacl -m default:group:audit:r-x /data/finance

# what people usually mean: existing children AND future ones, in one pass
hdfs dfs -setfacl -R -m group:audit:r-x,default:group:audit:r-x /data/finance

# remove one named entry / drop the default ACL / strip every extended entry
hdfs dfs -setfacl -x group:audit /data/finance
hdfs dfs -setfacl -k /data/finance
hdfs dfs -setfacl -b /data/finance

An ACL is capped at 32 entries per inode, counting the base owner, group, other, and mask entries. That ceiling is low enough to reach on a directory that has accumulated one named group per project over a few years, and hitting it is a useful signal rather than a limitation to route around: at that point the answer is group-based policy, not more entries.

The mask entry, and why chmod g+w does not do what you think

The moment an inode gains an extended ACL it also gains a mask entry, and the mask is a ceiling. It caps every named user entry, every named group entry, and the owning-group entry. It does not touch the owner entry and it does not touch other. An entry granting rwx underneath a mask of r-x is effectively r-x, and getfacl says so in a trailing comment rather than rewriting the entry.

$ hdfs dfs -getfacl /data/finance/ledger
# file: /data/finance/ledger
# owner: etl
# group: finance
user::rwx
user:svc_ingest:rwx             #effective:r-x
group::rwx                      #effective:r-x
group:audit:r-x
mask::r-x
other::---

$ hdfs dfs -ls /data/finance
-rwxr-x---+  3 etl finance  81923311 2026-07-02 04:11 /data/finance/ledger
#    ^^^ this triad is the mask, not the finance group   ^ and this is your only warning

Now the part that catches almost everyone. Once an inode carries an ACL, the middle triad that ls prints is no longer the owning group's permissions - it is the mask. And chmod's group digit does not edit the owning-group entry either; it rewrites the mask. So hdfs dfs -chmod 750 on the path above, typed by an operator who only wanted to tidy an inconsistent mode string, quietly clamps svc_ingest from rwx down to r-x, and the ingest job starts failing its writes with no ACL entry having been touched by anybody. The opposite mistake is worse: chmod 770 raises the mask and hands write access to every named entry that was authored with rwx and deliberately capped below it.

There is a second-order effect that bites during routine changes. setfacl -m recomputes the mask automatically as the union of everything it could grant, unless you supply an explicit mask:: entry in the same command. So adding one apparently harmless read entry to a path whose mask was deliberately tightened widens the mask back out and restores permissions that somebody removed on purpose weeks earlier. Where the mask is load-bearing, set it explicitly on every command that touches the ACL, and treat an implicit recalculation as a change to be reviewed.

The + that ls appends to a mode string is the only visual warning any of this is in play. Read it as a flag saying the mode string on that line is not telling you the truth, and reach for getfacl before drawing a conclusion.

How the effective permission is actually computed

The NameNode evaluates a path one component at a time. To touch /data/finance/2026/ledger the caller needs execute on /, on /data, on /data/finance, and on /data/finance/2026, and only then the requested permission on ledger itself. Traversal is checked first and from the top, so a missing bit four levels up yields a denial that names the leaf - which sends people to inspect the wrong inode, sometimes for hours.

For one inode, the check walks classes in a fixed order and stops at the first class that matches:

OrderMatches whenPermission used
1the username equals the inode's ownerthe owner entry, mask not applied
2a named user entry matches the usernamethat entry, clamped by the mask
3the owning group, or any named group entry, is in the user's resolved group listthe union of all matching entries, clamped by the mask
4nothing above matchedthe other entry, mask not applied

The word doing the damage is stops. These are not four filters a request must survive; they are four mutually exclusive cases. If step 1 matches and the owner entry reads r--, a write fails - and it fails even though other says rwx, and even though the user sits in a named group that was granted write. The owner of a file can have strictly less access to it than a stranger does, which reads as a bug until you see the ordering.

Step 2 has the same shape and is genuinely useful: a named user entry carrying no bits at all is an effective deny for exactly that person, whatever any group grants them, because the match happens and consumes the decision. HDFS ACLs have no explicit deny entry - a matching entry with an empty permission set is how you write one.

Step 3 is the one exception to first-match-wins, in a narrow sense. It does not stop at the first matching group; it takes the union of every matching group entry, applies the mask to the result, and allows if the requested bit survives. Belonging to three groups can only help you at step 3, and cannot help you at all if step 2 already matched.

Two authorities sit outside the algorithm entirely. The owner may always change the mode and the ACL of their own inode whatever the current bits say, which is why a user cannot permanently lock themselves out of their own file. And the superuser skips the whole thing without a check ever running.

The sticky bit and the shared directory problem

A world-writable shared directory has an unpleasant property that follows directly from how deletion is authorized: removing an entry requires write and execute on the parent directory and nothing whatsoever on the entry itself. Read-only mode bits on a file protect its contents and never its existence. So in a directory anyone can write to, anyone can delete anyone else's data.

The sticky bit closes that hole. Set it with hdfs dfs -chmod 1777 /tmp - it appears as a t in the final position of the mode string - and deleting or renaming an entry inside that directory is restricted to the entry's owner, the directory's owner, and the superuser, regardless of the write bit. On a file the bit is stored, displayed, and ignored; it is meaningful only on directories.

The place it is most often missing is team-created shared scratch space, where somebody reproduced the permissions of /tmp from memory and dropped the leading digit. The directory behaves identically right up until two teams genuinely share it, and then a cleanup job removing what it believes is its own output takes a neighbouring dataset with it. Nobody investigating that incident starts by suspecting a permissions bug, because from the audit log the delete was performed by a user who was allowed to perform it.

The superuser, the supergroup, and why neither of them is root

HDFS has a superuser and it is not root. It is whatever identity the NameNode process itself runs as, conventionally hdfs. Root on a DataNode host has total power over the block files on that machine's disks and no standing at all in the namespace; the HDFS superuser has total power over the namespace and no automatic shell anywhere. These are two genuinely different administrator roles that people habitually treat as one, and keeping them separate - different humans, different escalation paths - is one of the cheapest real controls available on a cluster.

Membership of the group named by dfs.permissions.superusergroup, default supergroup, confers the same bypass. This is the intended route for granting administrative access to humans, and it carries a footgun that follows directly from how groups are resolved. That group is looked up through the NameNode's group mapping like any other. Creating a local Unix group called supergroup on the NameNode host and adding people to it works exactly as long as the mapping consults the local host. Repoint the mapping at a corporate directory in which no such group exists and every administrator silently loses the bypass; if the directory happens to contain a group of that name administered by somebody else entirely, you have instead granted a bypass you never intended and cannot see.

A related and separate property, dfs.cluster.administrators, governs the administrative interfaces - the web UI and the metrics endpoints - rather than the namespace. It is a different list and is very often left broader than the superuser group, which is worth checking during a review: a stack trace, a configuration dump, or a metrics scrape reveals more about a cluster than most people assume when they widen it.

The rule that follows is that superuser access should be rare, individually attributable, and reviewed. Every operation performed as the superuser bypasses everything described above and appears in the audit log with no denial ever recorded anywhere, because no check was run to produce one.

Proxy users - one service acting as many people

HiveServer2, Oozie, Knox, Livy, and the various history servers all share a shape of problem. Each runs under its own service identity with its own keytab, and each needs to read data on behalf of whichever end user submitted the request. Granting the service read access to everything would collapse every user's permissions into one, and there is no mechanism by which a service could acquire a user's own Kerberos credentials.

Impersonation is the answer, and it is deliberately narrow. The service authenticates normally as itself - that is the real user - and then performs each operation as an effective user it names in the RPC. The NameNode permits this only when configuration explicitly says that this service may impersonate this person from this host.

<!-- core-site.xml, read by the NameNode -->
<property>
  <name>hadoop.proxyuser.hive.hosts</name>
  <value>hs2-1.corp.example.com,hs2-2.corp.example.com</value>
</property>
<property>
  <name>hadoop.proxyuser.hive.groups</name>
  <value>analysts,data_science</value>
</property>

Three axes are constrained independently. .hosts restricts where an impersonation request may originate, .groups restricts who may be impersonated by group membership, and .users does the same by name. An axis left unset blocks everything, but * is an explicit "any" - and the pair hosts=* with groups=* is the most dangerous single line in a typical Hadoop configuration. It means that whoever can read that service's keytab can read every file belonging to every user in the cluster, and that keytab sits on a host a surprising number of people can reach.

Two operational notes. The failure message is precise and worth recognising on sight - User: hive is not allowed to impersonate alice points at the proxy configuration, not at anything wrong with Alice's own permissions, and people lose time treating it as an ACL problem. And these settings are refreshable without restarting the NameNode, via hdfs dfsadmin -refreshSuperUserGroupsConfiguration, which is a relief the first time you need to add a host during business hours.

The subtlety worth internalising is that impersonation is the thing keeping per-user authorization alive through a shared service, and it can be switched off. Run HiveServer2 without it and every query executes as the hive identity: the HDFS layer sees one user for the entire organisation, the ACLs on the underlying files stop distinguishing anybody, and all real authorization has moved up into the SQL engine's own policy layer whether or not anyone decided that. It is a defensible architecture. It is not a defensible accident, and it is usually an accident.

Delete is a rename, so trash has its own permission story

With fs.trash.interval set to a non-zero number of minutes, deleting through the shell does not remove anything. It renames the path into a trash directory under the caller's home. That changes which permissions the operation needs. A plain delete needs write and execute on the parent directory; a delete-to-trash needs those and the ability to create the destination inside the user's own home directory. Accounts whose home directory was never created, or which ended up owned by somebody else during a migration, hit a failure at the trash step on a delete the permission model would otherwise have allowed - and the error points at a trash path the user has never heard of.

Because the rename is a rename, the data has not gone anywhere. It still occupies space against any quota, and it remains readable by whoever could read it before, until expiry catches up with it. And because a rename cannot cross an encryption zone boundary, a zone is given its own trash location instead of using the user's home; that mechanism and its capacity consequences are covered in HDFS Encryption Zones. The checkpoint and expiry mechanics themselves belong to HDFS Trash.

-skipTrash bypasses the entire path and performs a real delete, which is why it appears in every cleanup script that has ever been written, and why the sticky bit from two sections above is the control that actually protects a shared directory rather than anything about trash retention.

Snapshots freeze the ACL along with the data

A snapshot captures inode state, and owner, group, mode, and ACL are inode state. The read-only copies reachable under a .snapshot path therefore carry the permissions that were in force at the instant the snapshot was taken, not the ones in force now.

The security consequence is the one that gets missed during an incident. Tightening an ACL because the wrong team could read a dataset fixes the live path and does absolutely nothing to the snapshots. Where a nightly policy has been running for a month, thirty readable copies of that data sit under .snapshot, each carrying the old permissive ACL, each addressable by a path any of those users can type into a shell. Revoking access to sensitive data is not finished until the snapshots that predate the revocation are gone, and that step appears on almost no permission-change checklist.

In the other direction the immutability is protective. Nothing can be written through a snapshot path at all, so a snapshot cannot be used to modify data or to escalate. The right to create one is also separate from ordinary permissions: an administrator must first mark a directory snapshottable, after which its owner may take snapshots. The copy-on-write mechanics and the retention tradeoffs are covered in HDFS snapshots architecture.

Ranger replaces the check rather than stacking on top of it

HDFS exposes a formal extension point for authorization. dfs.namenode.inode.attributes.provider.class names a class the NameNode loads at startup, and that class supplies an access enforcer the NameNode calls instead of its built-in permission check. The Apache Ranger HDFS plugin is such a class. Apache Sentry, before it was retired, took a different route entirely and synchronised Hive privileges down into synthetic ACLs written onto the NameNode - which is why clusters with Sentry in their history show ACL entries that nobody on the current team remembers authoring and nobody dares remove.

Because the provider substitutes for the native check rather than running after it, the composition is not an intersection. The Ranger enforcer evaluates its own policies against the path and identity and reaches one of three outcomes. An explicit deny policy denies, and no mode bit can override that. An explicit allow policy allows - regardless of what the mode bits and ACLs on that inode say, which is the part that surprises people who expect a filter. And when no policy matches at all, the behaviour turns on a single flag in the plugin's configuration, xasecure.add-hadoop-authorization: true and the native POSIX and ACL check runs as a fallback, false and the unmatched request is simply denied.

That flag deserves more respect than it usually gets. Set to false on a cluster whose access has always been expressed in mode bits, it denies everything the moment the plugin loads: a spectacular, unambiguous, quickly-reverted outage that nobody repeats. Set to true, which is the common choice and the safe-looking one, it produces the more dangerous steady state - two authorization systems live at once, either of which can grant, and no single command anywhere that shows you their union.

Which is the real operational cost. Once a provider is installed, getfacl stops being an answer and becomes half of one; a path may be readable by people who appear nowhere in its ACL and never will. Policies are pulled by the plugin on a polling interval measured in tens of seconds, so a revocation in the admin UI is not in force at the moment somebody clicks save, and the window is long enough to matter when you are racing a departing employee. And the decisions land in the policy engine's own audit store rather than the NameNode audit log, so neither record is complete on its own. The centralised policy model, the tag-based rules, and the Atlas integration are covered in Apache Ranger.

Auditing effective access

"Who can read this directory" has no single command that answers it, which is precisely why the question keeps getting answered wrongly. Assembling an answer takes four ingredients, and skipping any one of them is how reviews pass on clusters that are wide open.

Start with hdfs dfs -getfacl -R over the subtree. It reports the ACLs including the #effective: annotations, and it is the only view that does not lie: reading mode strings out of ls misreports the group triad on every path that carries an ACL. Second, walk the ancestors with hdfs dfs -ls -d on each component from the root down, because a subtree nobody can traverse is unreachable however permissive its leaves look, and the inverse - one over-wide ancestor - is how data becomes visible through a path that no audit ever listed. Third, expand every identity with hdfs groups, so the group names in the ACL are resolved by the same authority that will resolve them at request time rather than by whatever the reviewer's own machine believes. Fourth, if an attributes provider is installed, export its policies too, because the first three ingredients no longer determine the outcome on their own.

For what actually happened, as opposed to what is possible, the NameNode audit log is the primary record. Each line carries the effective user, the client address, the command, the path, and whether the operation was permitted, so denials are recorded alongside successes and the log serves both compliance evidence and debugging. It is a log4j appender and can be routed somewhere durable without disturbing the NameNode's other logging. Two cautions: an operation performed by the superuser is logged but was never checked, so its presence proves nothing about anybody's permissions, and on a cluster running a policy plugin the authoritative record of a denial is in that plugin's audit store rather than here.

The habit worth building is to verify from outside. After any permission change, become the user - or drive the same impersonation path a service would use - and attempt the operation for real. Reading configuration back to yourself confirms what you typed. It does not confirm what the NameNode concluded, and those differ far more often than anyone expects.

The misconfigurations that actually cause incidents

dfs.permissions.enabled left at false. It disables the check outright: the NameNode still records owners, groups, and modes, still displays them, and consults none of them. It gets flipped during a migration to make a stubborn copy job finish and is never flipped back, and nothing about the cluster looks wrong afterwards, because every ACL anybody writes is stored and echoed back perfectly.

Meticulous ACLs on a simple-authentication cluster. The highest-severity item on this list, and the one most often defended on the grounds that the network is private. Effort spent on permissions before hadoop.security.authentication is kerberos buys tidiness, not access control.

A default ACL applied to a directory that already holds data. The new entries reach children created afterwards and nothing else. Every verification of the change passes, because the person verifying checks the directory rather than a file written last year.

chmod on a path that carries an ACL. The group digit rewrites the mask and silently clamps or widens every named entry underneath it. This is the failure that arrives as "the ingest job broke overnight and nobody changed its permissions", and it is true - nobody changed its permissions, somebody changed the ceiling above them.

777 as a debugging step. It closes the ticket, it is never reverted, and on a shared directory it also drops the sticky bit that was the only thing standing between two teams and each other's data.

Proxy user wildcards. hosts=* together with groups=* converts a service keytab into an unrestricted read of every user's files. It is usually introduced to unblock a deployment on a Friday.

A supergroup name that does not resolve. Either the administrators quietly lose their bypass and discover it during an outage, or a group of that name in the corporate directory - administered by a team that has never heard of the cluster - becomes a set of HDFS superusers.

Trusting a revocation immediately. Five minutes of group cache is long enough that a removal appears to have failed, which is long enough for somebody to escalate to a blunter instrument and cause a second incident on top of the first.

Assuming a policy engine narrows what HDFS permits. It does not. On a cluster with the plugin installed there are two independent routes to being granted access, and the one that is easier to inspect is not usually the one that decided.

HDFS authorization is a chain and it is only as strong as its weakest link: a username that is merely asserted unless Kerberos is on, a group list resolved by a host that may not know your users, mode bits whose middle triad is really a mask the moment an ACL exists, a check that stops at the first matching class rather than accumulating grants, and a pluggable enforcer that can replace the entire thing. Audit with getfacl and hdfs groups rather than ls, treat the mask and default ACLs as the two mechanisms most likely to be misunderstood, and remember that trash and snapshots both carry copies of your data past the moment you revoked access to the original.