August 25, 2026
When a 777 Directory Becomes a Security Issue: A Bug Bounty Story About State Tampering
Not every security finding is an RCE. Sometimes the interesting bugs are hidden in the assumptions developers make about the environment…

By julichaan
3 min read
Not every security finding is an RCE. Sometimes the interesting bugs are hidden in the assumptions developers make about the environment their software runs in.
During a recent bug bounty engagement, I spent some time reviewing a well-known open-source utility used to manage workstation configuration and automation. The target wasn't a web application, an API, or a cloud service. Instead, I focused on something much less glamorous: how the application stored and protected its local state.
The result was a valid security finding that highlighted a common but often overlooked issue: relying on the system's umask for security-sensitive directory permissions.
All identifying information has been intentionally redacted. The vendor, platform, and infrastructure details are omitted to respect disclosure boundaries.
Looking Beyond the Obvious
When reviewing software from a security perspective, most researchers naturally gravitate toward authentication mechanisms, network services, secrets management, or privilege boundaries.
However, local state management can be equally important.
Applications frequently store execution metadata, lock files, databases, caches, or "run once" markers that influence future behaviour. If an attacker can manipulate that state, they may be able to alter application logic without touching the source code or gaining elevated privileges.
While tracing the application's state persistence flow, I noticed the following pattern:
OpenFile: func(name string, flag int, perm fs.FileMode) (*os.File, error) {
dir, _ := filepath.Split(rawPath.String())
if err := os.MkdirAll(dir, 0o777); err != nil {
return nil, err
}
return os.OpenFile(rawPath.String(), flag, perm)
}OpenFile: func(name string, flag int, perm fs.FileMode) (*os.File, error) {
dir, _ := filepath.Split(rawPath.String())
if err := os.MkdirAll(dir, 0o777); err != nil {
return nil, err
}
return os.OpenFile(rawPath.String(), flag, perm)
}At first glance, nothing appears particularly dangerous.
The database file itself was created with restrictive permissions. The concern came from the parent directory being explicitly requested as 0777.
Understanding the Root Cause
The issue stems from the interaction between application permissions and the operating system's umask.
For those unfamiliar, umask acts as a permissions filter. When an application requests file or directory permissions, the operating system removes specific bits according to the active mask.
For example:
Requested ModeUmaskResult077702207550777002077507770000777
In many desktop environments, a restrictive umask prevents security problems from appearing.
The problem is that security-sensitive software should not depend on environmental defaults remaining secure.
If the application runs in a shared environment where umask 000 is configured—such as certain CI/CD runners, containers, development systems, or multi-user workstations—the state directory becomes writable by other users.
At that point, trust boundaries begin to blur.
Reproducing the Behaviour
The issue was easy to demonstrate safely.
- Configure a permissive mask:
umask 000umask 000- Remove the existing state database directory.
- Execute a normal application workflow that recreates the state.
- Inspect the resulting permissions:
ls -ld <state-directory>ls -ld <state-directory>The directory inherits permissions that are significantly broader than what would typically be expected for security-sensitive application state.
It's important to emphasize what this finding is not:
- It is not Remote Code Execution.
- It is not Privilege Escalation.
- It does not allow an external attacker to compromise the system.
The attack surface remains local.
Nevertheless, local security assumptions matter.
Why This Matters
A common mistake when assessing vulnerabilities is focusing exclusively on direct code execution.
Security is also about integrity.
If another local user can modify trusted application state, they may be able to:
- Delete execution history.
- Force "run once" automation to execute repeatedly.
- Alter internal assumptions used by workflows.
- Corrupt state information and influence future behaviour.
- Create difficult-to-diagnose operational issues.
In shared environments, these effects can become surprisingly impactful.
The finding therefore falls into an interesting category: a low-complexity issue whose impact depends heavily on deployment context.
FactorAssessmentAttack SurfaceLocal state directoryComplexityLowPrerequisitesShared environment and permissive umaskImpactState tampering and integrity lossExploitabilityContext dependentSeverityMedium
The Fix
The remediation was straightforward.
Instead of requesting a world-writable directory and relying on umask to remove permissions, the application should explicitly enforce the intended security boundary.
if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, err
}if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, err
}This ensures that only the owner can access the directory regardless of the surrounding environment.
The change is small, but it removes a security dependency that should never have existed in the first place.
A Broader Security Lesson
One of the most valuable lessons from bug bounty work is that impactful findings rarely look like movie scenes.
Most real-world vulnerabilities emerge from assumptions:
- Assuming environment variables are trusted.
- Assuming input will always follow expected formats.
- Assuming infrastructure defaults are secure.
- Assuming permission models behave consistently everywhere.
In this case, the application assumed that umask would always compensate for an overly permissive directory creation request.
That assumption held true most of the time.
Security issues often begin precisely where those assumptions stop being true.
Final Thoughts
This finding was ultimately accepted as a valid security hardening issue, not because it enabled immediate compromise, but because it introduced unnecessary security variance between environments.
And that's a lesson worth remembering.
The most dangerous bugs are not always the loudest ones. Sometimes they're a single permission bit away from becoming somebody else's problem.