Security
The security module provides portable name lookup, Unix process identity,
POSIX ACLs, and Windows access token information and security descriptor
manipulation.
Portable Identity Queries
security.user_name returns the
current target user on both platform families:
security.unix.user_name,
security.unix.user_id,
security.unix.group_name,
and security.unix.group_id
resolve accounts in the target's user and group databases. Calling these
functions when the current VFS context does not target a Unix system raises
sys.UnsupportedError.
Unix Identity
security.unix.id() returns information
about the identity under which the shell or VFS process is running:
import security.unix:
- id
- group_name
let identity = id()
echo "uid=$(identity.uid) gid=$(identity.gid)"
echo "effective uid=$(identity.euid) gid=$(identity.egid)"
for gid = identity.groups
echo "group $gid: $(group_name(gid))"
POSIX ACLs
security.unix.Acl is an immutable collection
of security.unix.Ace entries. The object model
is available on every platform. Filesystem get and set operations are supported
on Linux and FreeBSD.
import fs
import security.unix:
- Acl
- Ace
- Permission
- id
let identity = id()
let rw = Permission(:READ:, :WRITE:)
let r = Permission(:READ:)
let access = Acl $
$ Ace.user_obj $rw
$ Ace.user $identity.euid $r
$ Ace.group_obj $r
$ Ace.mask $r
$ Ace.other()
fs.set_acl config.ini $access
Use fs.acl to read
stored ACL metadata. It returns nil when no ACL is stored; it does not
construct an ACL from file mode bits. Pass nil to
fs.set_acl to
remove the ACL. Set default: true to operate on a directory's inheritable
default ACL.
Acl validates the required base entries and requires a mask when named user
or group entries are present. It preserves the supplied mask without
recalculating it.
NFSv4 ACLs
security.nfs4.Acl is an immutable collection
of security.nfs4.Ace entries. The object
model is available on every platform. Filesystem get and set operations are
supported on FreeBSD only.
import fs
import security.nfs4:
- Acl
- Ace
- Mask
import security.unix:
- id
let identity = id()
let read = Mask(:READ_DATA:, :READ_ATTRIBUTES:, :READ_ACL:)
let access = Acl $
$ Ace.owner type: :ALLOW: mask: (Mask())
$ Ace.user $identity.euid type: :ALLOW: mask: $read
$ Ace.everyone type: :DENY: mask: $read
fs.set_acl config.ini $access
Pass kind: :NFS4: to fs.acl to read an NFSv4 ACL instead of the default
POSIX one; a built ACL supplies its format to fs.set_acl. Declarative ACE
sequences require kind: :NFS4:. default:
true is not valid with an NFSv4 ACL — inheritance is expressed through
Ace flags instead of a separate default-ACL
object. Unlike a POSIX ACL, an NFSv4 ACL is a file's native security
descriptor: it can be replaced with fs.set_acl, but FreeBSD provides no
operation to remove it back to "none".
macOS ACLs
security.macos.Acl is an immutable
collection of security.macos.Ace entries.
The object model is available on every platform. Filesystem get and set
operations are supported on macOS only.
Unlike NFSv4 or POSIX.1e ACL entries, macOS resolves every principal to a
UUID before it reaches the file's ACL, so an Ace's principal is a
uuid.Uuid rather than a special-cased qualifier:
import fs
import security.macos:
- Acl
- Ace
- Mask
let owner = uuid.Uuid "..."
let read = Mask(:READ_DATA:, :READ_ATTRIBUTES:, :READ_SECURITY:)
let access = Acl $
$ Ace.allow $owner mask: $read
fs.set_acl config.ini $access
Pass kind: :MACOS: to fs.acl to read a macOS extended ACL instead of the
default POSIX one; a built ACL supplies its format to fs.set_acl.
Declarative ACE sequences require kind: :MACOS:.
default: true is not valid with a macOS ACL, the same as with an NFSv4
one. Unlike an NFSv4 ACL, a macOS extended ACL is an optional overlay on top
of POSIX permissions, so it can be removed back to "none" with fs.set_acl
config.ini nil kind: :MACOS:.
Since ACL principals are UUIDs, building or inspecting a macOS ACE usually
means converting between a Unix uid/gid and its UUID.
security.macos.uuid_for_uid
and
uuid_for_gid go from
id to UUID;
id_for_uuid goes the
other way, returning which kind (:UID: or :GID:) the UUID resolved to
alongside the id itself, since a bare UUID doesn't say which it is:
import security.macos
import security.unix: id
let owner = security.macos.uuid_for_uid id().euid
let access = Acl $
$ Ace.allow $owner mask: $read
fs.set_acl config.ini $access
let kind id = security.macos.id_for_uuid owner
echo "$kind $id" # UID 501
security.unix.user_name and
group_name also accept a
UUID directly on macOS, resolving it internally, so an ACE principal can be
turned into a name without a separate id_for_uuid call.
Windows Access Tokens
security.windows.token_info()
returns a TokenInfo captured for the
active Windows target:
import security.windows:
- token_info
let token = token_info()
let account = token.user_sid.lookup()
echo "$(account.qualified_name) ($(token.user_sid))"
echo "elevated: $(token.is_elevated)"
for group = token.groups
echo "$(group.sid): enabled=$(group.enabled) deny-only=$(group.use_for_deny_only)"
The token also exposes its default owner, primary group, optional logon SID,
and complete group membership attributes. is_elevated reports whether the
Windows token has administrator rights.
Resolving SIDs and Account Names
Use Sid.lookup() for SID-to-name
resolution and
SidName.lookup for either
direction:
import security.windows:
- SidName
let admins = SidName.lookup "BUILTIN\\Administrators"
echo "$(admins.sid): $(admins.qualified_name) ($(admins.kind))"
echo $admins.sid.lookup().qualified_name
SIDs and other identity types can always inspected on a Unix host once obtained, but resolution is only possible on an active Windows VFS target.
Filesystem Security Descriptors
Windows file ownership and access control are represented by
SecDesc,
Acl, and
Ace:
SecDesccarries selected owner, group, DACL, and SACL components plus native control flags.Aclis an immutable ordered collection of access-control entries.Aceexposes its trustee SID, access mask, inheritance flags, and native ACE type.
Read selected components with
fs.windows.sec_desc:
import fs.windows:
- sec_desc
let desc = sec_desc config.ini
echo "owner: $(desc.owner.lookup().qualified_name)"
if desc.dacl == nil
echo "DACL: null"
else
for ace = desc.dacl.aces
echo "$(ace.type) $(ace.sid) $(ace.mask)"
Owner, group, and DACL are loaded by default. Request sacl: true only when
the caller has the required Windows access rights and privileges.
SecDesc.with creates a modified descriptor while preserving other components.
Apply a modified descriptor with
fs.windows.update_sec_desc:
import fs.windows:
- sec_desc
- update_sec_desc
import security.windows:
- SidName
let desc = sec_desc config.ini
let owner = (SidName.lookup "BUILTIN\\Administrators").sid
update_sec_desc config.ini $ desc.with :owner
Changing a DACL normally requires WRITE_DAC; changing an owner normally
requires WRITE_OWNER or an applicable ownership privilege. Reading or
writing a SACL normally requires ACCESS_SYSTEM_SECURITY and the corresponding
security privilege. Windows returns sys.PermissionDeniedError or a more
specific native error when the VFS context lacks the required authority.
SecDesc, Acl, Ace, and Sid support native binary conversion.
Pure inspection and manipulation of descriptors works on Unix hosts.
VFS Behavior
Security operations follow the active VFS context just like filesystem and process operations. A Linux interpreter connected to Windows receives Windows token, SID, descriptor, and error semantics; a Windows interpreter connected to Unix receives UID/GID semantics. Nesting SSH, container, WSL, or elevation contexts changes which identity is queried.