Course outline · 0% complete

0/28 lessons0%

Course overview →

Permissions: who may touch a file

lesson 7-4 · ~10 min · 24/28

The metadata that says no

Lesson 7-3 showed that every file carries metadata. One piece of it, permissions, is the gatekeeper. It tells the OS who may do what with the file, and the OS checks it on every open.

You will collide with permissions in your first weeks of real work: a script failing with PermissionError, a downloaded tool that refuses to run until chmod +x, an SSH key that ssh rejects because others can read it. This lesson is so those moments take seconds instead of an afternoon.

On macOS and Linux, each file stores three permission flags for three audiences.

FlagAllows
rreading the bytes
wwriting or changing them
xexecuting the file as a program

The audiences are the file's owner, the file's group, which is a named set of users, and others, meaning everyone else.

That is 9 yes-or-no switches per file, and the kernel consults them before your code sees a single byte. A denied check surfaces in Python as a PermissionError, because the request crossed into the OS and the OS said no.

A read-only file rejecting a write

Mode 0o400 allows the owner to read and nothing else at all.

import os
import stat

with open("secret.txt", "w") as f:
    f.write("api-key-123\n")

os.chmod("secret.txt", 0o400)
print(stat.filemode(os.stat("secret.txt").st_mode))

try:
    open("secret.txt", "w")
    print("write allowed")
except PermissionError:
    print("write blocked: the OS refused before one byte moved")

with open("secret.txt") as f:
    print("read still works:", f.read(), end="")

Output

-r--------
write blocked: the OS refused before one byte moved
read still works: api-key-123

os.chmod sets the permission bits and stat.filemode renders the 9 switches the way ls -l does. Read the string in groups of three after the leading dash: r-- for the owner, then --- for the group, then --- for others.

0o400 is exactly the mode ssh demands for private key files. If the group or other bits are set, ssh refuses the key outright, which is the single most common reason a freshly copied key does not work.

Reading and writing the numbers

Permissions are usually written as three octal digits, base 8, one per audience, because each digit packs the three flags as a sum where r is 4, w is 2, and x is 1.

DigitFlags
7rwx
6rw-
5r-x
4r--
0---

Python spells octal with the 0o prefix, the same idea as 0b and 0x from lesson 2-1. The combinations you will actually type:

ModeMeaningTypical use
755owner rwx, everyone else r-xprograms and scripts
644owner rw-, everyone else r--ordinary data files
600owner rw-, no one else anythingprivate config, credentials
400owner read-onlySSH private keys

In the terminal the tool is chmod, short for change mode, as in chmod 755 deploy.sh, or the shortcut chmod +x tool.sh to switch on execute for everyone. The execute bit is why a freshly written script answers Permission denied, since the shell asked the OS to execute a file whose x switches are off.

One honest caveat: the root user, the administrator account the OS itself uses, bypasses these checks. Permissions protect against accidents and other users, not against whoever owns the machine.

Making a script executable

Mode 755 gives the owner full access and everyone else read plus execute.

import os
import stat

with open("tool.sh", "w") as f:
    f.write("echo hi\n")

os.chmod("tool.sh", 0o755)

print(stat.filemode(os.stat("tool.sh").st_mode))

Output

-rwxr-xr-x

The octal literal must be written as 0o755, since a plain 755 is the decimal number seven hundred fifty-five and a completely different bit pattern. This is exactly what chmod +x does when it makes a script runnable.

Reading the output string confirms the intent: rwx for the owner, then r-x twice for group and others. Everyone can run it and only the owner can change it, which is the normal arrangement for an installed program.

Permission denied on a script you can read

A cloned project whose ./deploy.sh answers "Permission denied" while cat reads it fine has its execute bit off, and chmod +x deploy.sh fixes it.

Read and execute are separate switches. cat needs r, running the file as a program needs x, and only the x bit is missing.

ActionBit required
cat deploy.shr
./deploy.shx
editing itw

That r and x are independent is a feature rather than an inconvenience. Config files stay readable but not runnable by design, and a data file that could be executed would be a security problem rather than a convenience.