# PYTHON-LANG-SEC-101: Insecure File Permissions via os.chmod

> **Severity:** MEDIUM | **CWE:** CWE-732 | **OWASP:** A05:2021

- **Language:** Python
- **Category:** Python Core
- **URL:** https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-101
- **Detection:** `pathfinder scan --ruleset python/PYTHON-LANG-SEC-101 --project .`

## Description

Python's os.chmod() and os.fchmod() functions set the permission bits on files and
directories. Setting permissions that are too broad — for example, 0o777 (world-readable,
world-writable, world-executable) or 0o666 (world-readable and world-writable) — allows
any user on the system to read, modify, or execute the file. This can lead to unauthorized
data access, privilege escalation, or injection of malicious content into files that are
later executed or interpreted by a privileged process.

Common dangerous patterns include using octal literals like 0o777, 0o666, 0o644 on sensitive
files, or using stat constants like stat.S_IWOTH (world-write) and stat.S_IROTH (world-read)
on configuration files, temporary files containing secrets, or executable scripts.

The principle of least privilege dictates that files should be given only the minimum
permissions necessary. Configuration files should typically be 0o600 (owner read/write only),
scripts should be 0o700 (owner execute only), and shared files should not be world-writable.


## Vulnerable Code

```python
import uuid
import os
import re
import logging
import logging.config

# SEC-101: insecure file permissions
os.chmod("/tmp/data", 0o777)
os.fchmod(3, 0o666)
```

## Secure Code

```python
import os
import stat
import tempfile

# SECURE: Restrict config files to owner read/write only
def write_config(path: str, content: str) -> None:
    with open(path, 'w') as f:
        f.write(content)
    os.chmod(path, 0o600)  # owner rw only

# SECURE: Scripts executable only by owner
def write_script(path: str, content: str) -> None:
    with open(path, 'w') as f:
        f.write(content)
    os.chmod(path, 0o700)  # owner rwx only

# SECURE: Temporary files with restrictive permissions
def create_temp_file() -> str:
    fd, path = tempfile.mkstemp()
    os.fchmod(fd, 0o600)  # restrictive before writing any data
    os.close(fd)
    return path

# SECURE: Shared files readable by group but not world
def write_shared_file(path: str, content: str) -> None:
    with open(path, 'w') as f:
        f.write(content)
    os.chmod(path, 0o640)  # owner rw, group r, no world access

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, QueryType

class OSModule(QueryType):
    fqns = ["os"]


@python_rule(
    id="PYTHON-LANG-SEC-101",
    name="Insecure File Permissions",
    severity="MEDIUM",
    category="lang",
    cwe="CWE-732",
    tags="python,file-permissions,chmod,CWE-732",
    message="Overly permissive file permissions detected. Restrict to minimum required permissions.",
    owasp="A05:2021",
)
def detect_insecure_permissions():
    """Detects os.chmod/fchmod/lchmod with overly permissive modes."""
    return OSModule.method("chmod", "fchmod", "lchmod")
```

## How to Fix

- Use 0o600 for files containing secrets, credentials, private keys, or sensitive configuration — owner read/write only.
- Use 0o700 for executable scripts that should only be run by their owner, and 0o750 if group execution is needed.
- Never use 0o777 or 0o666 in production code; if a file must be shared, use group permissions (e.g., 0o660) rather than world permissions.
- Set the umask appropriately at process startup (os.umask(0o077)) to ensure files are created with restrictive defaults.
- Audit existing files with overly broad permissions using find with -perm -o+w or equivalent and restrict them.

## Security Implications

- **Sensitive Data Exposure:** Files containing credentials, private keys, API tokens, or user data that are
made world-readable (permissions including o+r) can be read by any user on the
system. In multi-tenant environments or on compromised hosts, this directly exposes
secrets to other users or processes.

- **Privilege Escalation via World-Writable Files:** World-writable files (permissions including o+w) that are later read and executed
by a privileged process — such as a cron job, init script, or SUID binary — can
be modified by a low-privilege attacker to inject arbitrary commands. This is a
classic Unix privilege escalation vector.

- **Log Tampering and Evidence Destruction:** Log files or audit trails set to world-writable permissions allow any user to modify
or truncate log content, destroying evidence of malicious activity. Attackers routinely
clear or modify logs after compromise to hinder incident response.

- **Configuration Injection:** Configuration files with world-write permissions can be modified by any local user
to change application behavior, inject malicious settings, or redirect processing to
attacker-controlled resources. This is particularly dangerous for web server configs,
database connection strings, and application settings files.


## FAQ

**Q: What permission values are considered insecure?**

Any permission that grants write access to "others" (the world) is considered insecure.
In octal notation, the last digit controls world permissions: digits 2, 3, 6, and 7 all
include world-write. So 0o777, 0o776, 0o666, 0o667, 0o757, 0o646, etc. are all flagged.
World-readable (0o644, 0o444) may be acceptable for public files but is flagged for
sensitive files. The rule focuses primarily on world-writable permissions as the highest risk.


**Q: What about shared directories that need broad permissions?**

For directories that need to be shared between users, prefer group permissions over world
permissions. Add all relevant users to a shared group and use 0o770 (owner and group
read/write/execute) instead of 0o777. The sticky bit (0o1777) on shared directories
prevents users from deleting each other's files without granting unnecessary write access.


**Q: Is 0o644 safe for configuration files?**

0o644 (owner read/write, group and world read-only) is acceptable for non-sensitive
configuration files that need to be readable by multiple users. However, for files
containing passwords, API keys, TLS private keys, or other secrets, use 0o600
(owner read/write only). The presence of world-readable permissions on secret files
is a security risk in multi-user or shared environments.


**Q: How should I handle file permissions for web-served static files?**

Static files served by a web server should typically be owned by the web server user
or a deployment user with group read permission, not world-writable. A common pattern
is 0o644 for files and 0o755 for directories, where the web server runs as a specific
user/group. Avoid making web-served directories world-writable as this allows any
local user to plant malicious files.


**Q: Does this rule flag os.chmod calls in test code?**

Yes. Test code that uses overly permissive permissions to set up test fixtures can
create security risks if the tests run in shared CI environments or leave files behind.
It is good practice to use restrictive permissions even in tests and explicitly set
permissions in setup/teardown to ensure test isolation.


## References

- [Python docs: os.chmod()](https://docs.python.org/3/library/os.html#os.chmod)
- [CWE-732: Incorrect Permission Assignment](https://cwe.mitre.org/data/definitions/732.html)
- [OWASP Security Misconfiguration](https://owasp.org/Top10/A05_2021-Security_Misconfiguration/)
- [Unix File Permissions Reference](https://www.gnu.org/software/coreutils/manual/html_node/File-permissions.html)
- [Python tempfile module — secure temporary files](https://docs.python.org/3/library/tempfile.html)

---

Source: https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-101
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
