# 51. Audit Hooks

# 51. Audit Hooks

Audit hooks are CPython’s runtime mechanism for observing security-sensitive and operationally important events. They let embedders, security tools, test harnesses, and monitoring systems receive notifications when Python code performs actions such as importing modules, opening files, starting subprocesses, compiling code, executing dynamic code, loading native extensions, or changing tracing state.

Audit hooks do not replace operating system sandboxing. They are an observability and policy-enforcement mechanism inside the Python runtime.

At the Python level, audit hooks are exposed through `sys.addaudithook`.

```python id="l8x6qw"
import sys

def hook(event, args):
    print(event, args)

sys.addaudithook(hook)

open("example.txt", "w")
```

When audited operations occur, CPython calls registered hooks with an event name and an argument tuple.

## 51.1 Why Audit Hooks Exist

Python programs can perform many dynamic operations.

Examples:

```text id="ql8v1k"
import code by name
load extension modules
open files
create sockets
start subprocesses
compile source strings
execute dynamic code
inspect stack frames
set trace functions
modify import paths
deserialize data
```

These operations are powerful. They also matter for security, debugging, compliance, and embedding.

An embedding host may want to know when embedded Python code opens a file.

A security monitor may want to block subprocess creation.

A test harness may want to detect dynamic code execution.

Audit hooks provide one runtime-level place to observe such events.

## 51.2 Basic Audit Hook

A hook is a callable with this shape:

```python id="wq3jcp"
def hook(event, args):
    ...
```

Register it:

```python id="otqurj"
import sys

def audit_hook(event, args):
    print("event:", event)
    print("args:", args)

sys.addaudithook(audit_hook)
```

After registration, the hook receives audit events.

Example:

```python id="00z7l6"
import sys

def hook(event, args):
    if event.startswith("open"):
        print(event, args)

sys.addaudithook(hook)

with open("data.txt", "w") as f:
    f.write("hello")
```

Output shape:

```text id="91454a"
open ('data.txt', 'w', 524865)
```

The exact argument values depend on the event and CPython version.

## 51.3 Event Names

Audit events are identified by string names.

Examples include:

```text id="hqijqn"
open
import
compile
exec
eval
subprocess.Popen
socket.connect
os.chdir
os.remove
os.rename
ctypes.dlopen
sys.settrace
sys.setprofile
```

Event names usually follow the operation or module that triggers them.

Some are broad:

```text id="xgw0f9"
open
compile
exec
import
```

Some are more specific:

```text id="uzwgki"
subprocess.Popen
socket.connect
ctypes.dlopen
os.system
```

A hook can filter by prefix or exact event name.

```python id="p5w5gx"
def hook(event, args):
    if event == "subprocess.Popen":
        ...
```

## 51.4 Event Arguments

The `args` parameter is a tuple.

```python id="mqby6h"
def hook(event, args):
    print(type(args))
```

The meaning of each tuple element depends on the event.

For example, an `open` event may include the path, mode, and flags.

A `subprocess.Popen` event may include executable information and arguments.

A hook should be defensive because events differ.

```python id="z90mrn"
def hook(event, args):
    if event == "open":
        path = args[0] if args else None
        print("opening", path)
```

Audit hook code should not assume all events have the same shape.

## 51.5 Hooks Are Process-Local Runtime Observers

A Python audit hook observes events inside the current Python process.

It does not observe:

```text id="7f1apa"
other processes
kernel-level activity outside Python
native code that bypasses Python APIs
external programs after they start
operating system activity unrelated to CPython
```

If a C extension calls operating system APIs directly without emitting audit events, a Python-level hook may not see those operations.

Audit hooks improve visibility inside CPython. They are not a complete system security monitor.

## 51.6 Adding Hooks

Register hooks with:

```python id="l9t4wd"
sys.addaudithook(hook)
```

Hooks are called in registration order.

```python id="u8iv0t"
import sys

def first(event, args):
    print("first", event)

def second(event, args):
    print("second", event)

sys.addaudithook(first)
sys.addaudithook(second)

open("x.txt", "w")
```

Both hooks receive the event.

Once added, a Python-level audit hook cannot be removed through the public Python API.

This is deliberate. Audit hooks are intended to be hard for application code to silently disable.

## 51.7 Native Audit Hooks

Embedding applications can install native audit hooks from C before Python code runs.

This matters because Python-level hooks are installed after Python has already started executing.

A native embedding hook can observe earlier runtime events and is harder for Python code to bypass.

Conceptually:

```text id="4n0yqa"
host application starts
host installs native audit hook
CPython initializes
Python code runs
audit events flow to native hook
```

This is the strongest use case for audit hooks: embedding hosts that need policy or monitoring before user Python code executes.

## 51.8 Emitting Audit Events

Python code can manually emit audit events with:

```python id="x1nqug"
sys.audit(event, *args)
```

Example:

```python id="3zlh3f"
import sys

def dangerous_operation(path):
    sys.audit("myapp.dangerous_operation", path)
    return open(path).read()
```

A hook receives:

```text id="17xkze"
event = "myapp.dangerous_operation"
args = ("path-value",)
```

Custom audit events are useful for frameworks and embedded systems.

Use namespaced event names to avoid collisions:

```text id="7w7ggm"
myapp.plugin.load
myapp.tenant.start
myapp.policy.denied
```

## 51.9 Blocking Operations

An audit hook can block an operation by raising an exception.

```python id="4gxsxc"
import sys

def hook(event, args):
    if event == "subprocess.Popen":
        raise RuntimeError("subprocesses are disabled")

sys.addaudithook(hook)
```

Now code that tries to create a subprocess may fail.

```python id="xjwo8m"
import subprocess

subprocess.Popen(["echo", "hello"])
```

The audit hook exception interrupts the audited operation.

This makes audit hooks useful for policy enforcement, but enforcement is only as strong as the coverage of audited operations and the trustworthiness of code running in the process.

## 51.10 Hook Exceptions

If a hook raises an exception, the audited operation usually fails.

Example:

```python id="ys28m3"
def hook(event, args):
    if event == "open":
        raise PermissionError("file access blocked")
```

This can prevent file opening through Python’s normal `open` path.

Be careful. A hook that raises too broadly can break the interpreter, imports, logging, cleanup, or standard library internals.

Bad:

```python id="ja1fic"
def hook(event, args):
    raise RuntimeError("block everything")
```

This can make the process unusable.

Policy hooks should be precise.

## 51.11 Audit Hooks Are Not Sandboxes

Audit hooks cannot safely sandbox arbitrary untrusted Python code by themselves.

Reasons:

```text id="v7eqi5"
coverage is runtime-specific and API-specific
native extensions may bypass Python-level events
the same process memory is shared
malicious code may exploit bugs
hooks run inside the process they monitor
operating system resources are still shared
denial of service remains possible
```

Use operating system isolation for untrusted code:

```text id="71x5nb"
separate process
container
VM
seccomp or pledge-like controls where available
filesystem permissions
user namespaces
resource limits
network isolation
```

Audit hooks are useful inside a defense-in-depth design. They are not the boundary.

## 51.12 Import Auditing

Imports are audited.

A hook can observe imports:

```python id="ei05vi"
import sys

def hook(event, args):
    if event == "import":
        print("import:", args)

sys.addaudithook(hook)

import json
```

Import audit events can help detect:

```text id="5k7xeg"
unexpected dependencies
dynamic imports
plugin loading
native extension loading
module shadowing
policy violations
```

Blocking imports can be tricky because the import system itself needs many modules to function.

A safe policy usually blocks specific modules rather than broad import behavior.

```python id="9nwdcn"
def hook(event, args):
    if event == "import":
        name = args[0]
        if name == "subprocess":
            raise ImportError("subprocess disabled")
```

Even this may not prevent all subprocess use if the module was already imported or if native code bypasses it.

## 51.13 File Access Auditing

File operations are common audit targets.

```python id="vnqelr"
def hook(event, args):
    if event == "open":
        path = args[0]
        print("open", path)
```

A policy can restrict paths:

```python id="a2oxu9"
import os
import sys

allowed_root = os.path.abspath("/safe/root")

def hook(event, args):
    if event == "open":
        path = args[0]

        if isinstance(path, str):
            full = os.path.abspath(path)
            if not full.startswith(allowed_root + os.sep):
                raise PermissionError(full)

sys.addaudithook(hook)
```

This is useful for monitoring, but not a complete filesystem sandbox. Race conditions, symlinks, file descriptors, native code, and alternate APIs can complicate enforcement.

## 51.14 Subprocess Auditing

Subprocess creation is security-sensitive.

```python id="cx0krw"
import sys

def hook(event, args):
    if event == "subprocess.Popen":
        print("process:", args)

sys.addaudithook(hook)
```

A policy can block subprocesses:

```python id="r9a22q"
def hook(event, args):
    if event == "subprocess.Popen":
        raise PermissionError("subprocess disabled")
```

This prevents normal Python subprocess creation paths.

It does not prevent native code from calling OS process APIs directly if such native code is already loaded and able to do so.

## 51.15 Dynamic Code Auditing

Compilation and execution are audited.

Examples:

```python id="snjqde"
code = compile("x = 1", "<dynamic>", "exec")
exec(code)
```

Events may include:

```text id="ejwaqh"
compile
exec
```

A hook can observe these:

```python id="yvghk8"
def hook(event, args):
    if event in {"compile", "exec"}:
        print(event, args)
```

Dynamic code execution matters for security and debugging because it can obscure where behavior comes from.

Blocking all `compile` or `exec` events may break legitimate tools, import machinery, templates, dataclasses, typing machinery, and frameworks.

## 51.16 Tracing and Profiling Auditing

Changing trace or profile functions is audited.

Relevant operations include:

```text id="p9f5b9"
sys.settrace
sys.setprofile
```

These matter because tracing and profiling can inspect execution, frames, locals, and call flow.

A host may want to prevent untrusted code from installing trace hooks.

```python id="u067o7"
def hook(event, args):
    if event in {"sys.settrace", "sys.setprofile"}:
        raise PermissionError(event)
```

This is useful in controlled environments, but still not a complete sandbox.

## 51.17 Socket Auditing

Network operations can emit audit events.

A hook may observe connection attempts.

```python id="aamk62"
def hook(event, args):
    if event.startswith("socket."):
        print(event, args)
```

This can help monitor outbound connections.

A policy can restrict connections:

```python id="eoyujr"
def hook(event, args):
    if event == "socket.connect":
        sock, address = args
        host, port = address
        if port != 443:
            raise PermissionError("only HTTPS allowed")
```

Network policy is better enforced at the OS, container, or firewall layer. Audit hooks can add Python-level visibility.

## 51.18 `ctypes` and Native Loading

Loading native libraries is security-sensitive.

Audit events can observe operations such as dynamic library loading through `ctypes`.

```python id="pd9q14"
def hook(event, args):
    if event.startswith("ctypes."):
        print(event, args)
```

Native library loading can bypass many Python-level restrictions because native code can call OS APIs directly.

A restricted environment should usually block `ctypes`, extension loading, and other native escape hatches at multiple layers.

Audit hooks can detect or block some of these attempts.

## 51.19 Audit Hooks and Extension Modules

C extensions can emit audit events using CPython’s C API.

Conceptually:

```c id="da869j"
PySys_Audit("module.operation", "O", object);
```

This lets native modules participate in the audit system.

Extension authors should emit audit events for operations that are security-sensitive or operationally important.

Examples:

```text id="v0s4ar"
opening external resources
loading native libraries
starting processes
connecting to networks
executing user-provided code
changing global process state
```

## 51.20 Audit Hooks and Embedding

Embedding applications are a major target use case.

A host application can install a native audit hook before running user scripts.

Use cases:

```text id="zpn4ea"
application scripting
plugin runtimes
database embedded Python
game engines
automation systems
scientific workflow hosts
controlled notebook runtimes
```

The host can observe or deny operations according to policy.

Example policies:

```text id="ci4wpx"
no subprocesses
only read files under a workspace
no native library loading
no network access
no trace hook installation
log all imports
log dynamic code execution
```

For strong isolation, combine this with process-level controls.

## 51.21 Audit Hooks and Logging

A hook can log audit events.

```python id="psviyj"
import sys

def hook(event, args):
    if event in {"open", "subprocess.Popen", "socket.connect"}:
        print("audit", event, args)

sys.addaudithook(hook)
```

Be careful using `logging` inside hooks.

Logging may trigger audited operations, such as file writes, imports, lock acquisition, or formatting code.

A hook can accidentally create recursion.

Safer approaches:

```text id="ydc4jh"
write to a pre-opened low-level stream
filter aggressively
avoid imports inside the hook
avoid heavy formatting
avoid calling unknown code
buffer events carefully
```

## 51.22 Recursion in Audit Hooks

Audit hooks can trigger more audit events.

Example:

```python id="emkyzw"
def hook(event, args):
    print(event, args)
```

Printing may touch streams. Stream operations may trigger lower-level events in some contexts.

A hook should avoid complex side effects.

Use a recursion guard if needed:

```python id="d6ob90"
import sys
import threading

local = threading.local()

def hook(event, args):
    if getattr(local, "inside", False):
        return

    local.inside = True
    try:
        if event == "subprocess.Popen":
            sys.stderr.write(f"blocked: {event}\n")
            raise PermissionError(event)
    finally:
        local.inside = False
```

Keep hooks short and predictable.

## 51.23 Performance Cost

Audit hooks run during audited operations.

A hook that does expensive work can slow the whole program.

Bad:

```python id="uzjnci"
def hook(event, args):
    analyze_entire_stack()
    write_to_database()
    import_large_module()
```

Better:

```python id="8cvakt"
def hook(event, args):
    if event not in watched_events:
        return
    record_small_event(event, args)
```

Design hooks with the same care as logging in hot paths.

## 51.24 Hook Ordering

Hooks run in the order they were added.

If one hook raises an exception, later hooks may not run for that event.

This matters when combining logging and enforcement.

Example:

```text id="89s9uh"
hook 1 logs event
hook 2 blocks event
```

versus:

```text id="bywoy7"
hook 1 blocks event
hook 2 never sees event
```

For embedding hosts, install critical hooks as early as possible.

## 51.25 Audit Events Before Python Hooks

A Python-level hook is added only after Python code calls `sys.addaudithook`.

Events before that call are missed by that hook.

Example:

```python id="aamw8l"
import os
import sys

def hook(event, args):
    print(event, args)

sys.addaudithook(hook)
```

The import of `os` happened before the hook was installed.

Native hooks installed by the embedding host can observe earlier events.

This is important for security-sensitive hosts.

## 51.26 Inspecting Audit Events

A simple exploratory hook:

```python id="x3qyko"
import sys

def hook(event, args):
    if event.startswith(("open", "import", "subprocess", "socket", "ctypes")):
        print(event, args)

sys.addaudithook(hook)
```

Run a small program and observe what events occur.

This is useful for learning, but avoid broad printing in production because it can be noisy and recursive.

## 51.27 Event Stability

Audit event names and argument structures are part of a documented interface, but code should still be defensive across Python versions.

Good hook design:

```python id="fr15he"
def hook(event, args):
    if event == "open":
        if not args:
            return
        path = args[0]
        ...
```

Avoid fragile assumptions:

```python id="gwuhz0"
path, mode, flags, extra = args
```

unless the event contract guarantees that shape for the versions you support.

## 51.28 Policy Design

A policy hook should answer precise questions.

Good policy:

```text id="lx021w"
block subprocess.Popen
block ctypes.dlopen
block open outside /workspace
block socket.connect except approved hosts
log compile and exec
```

Poor policy:

```text id="a6n55w"
block anything suspicious
block all imports
raise on every event
inspect all arguments deeply
```

Audit hooks work best with narrow, explicit rules.

## 51.29 Allowlist Example

```python id="0quf3u"
import os
import sys

allowed_root = os.path.abspath("workspace")

def inside_allowed_root(path):
    full = os.path.abspath(path)
    return full == allowed_root or full.startswith(allowed_root + os.sep)

def hook(event, args):
    if event == "open":
        path = args[0]

        if isinstance(path, str) and not inside_allowed_root(path):
            raise PermissionError(path)

    if event == "subprocess.Popen":
        raise PermissionError("subprocess disabled")

    if event.startswith("ctypes."):
        raise PermissionError("native loading disabled")

sys.addaudithook(hook)
```

This is a useful teaching example. In production, path policy needs to handle symlinks, file descriptors, race conditions, platform paths, and native bypasses.

## 51.30 Audit Hooks and Tests

Tests can use audit hooks to detect forbidden behavior.

Example:

```python id="vnjdal"
import sys

events = []

def hook(event, args):
    if event in {"subprocess.Popen", "socket.connect"}:
        events.append((event, args))

sys.addaudithook(hook)
```

At the end of a test, assert no forbidden event occurred.

Because hooks cannot be removed, test suites must be careful. A hook installed in one test can affect later tests in the same process.

Often, audit-hook tests should run in a subprocess.

## 51.31 Audit Hooks and Import-Time Behavior

If a hook blocks file access or imports, it may affect imports themselves.

Importing Python modules can require:

```text id="un0vuo"
opening source files
opening bytecode files
reading directories
loading extension modules
executing module code
```

A file policy that blocks too much can break imports.

A practical policy may allow Python’s own library paths and restrict application data paths separately.

## 51.32 Audit Hooks and `eval`

Dynamic evaluation is audited.

```python id="evmgg8"
eval("1 + 2")
```

A hook can watch for this:

```python id="9sr21k"
def hook(event, args):
    if event in {"compile", "exec"}:
        print(event, args)
```

`eval` compiles and executes code. Depending on the path, both compilation and execution events may be relevant.

Blocking dynamic execution can break libraries that legitimately generate code.

Use policy based on context, not only the event name, when possible.

## 51.33 Audit Hooks and Serialization

Some serialization mechanisms can execute code or load classes dynamically.

Audit hooks may observe imports, code execution, or other side effects caused during deserialization.

However, audit hooks do not make unsafe deserialization safe.

Dangerous deserialization should be avoided or sandboxed externally.

For untrusted data, prefer safe formats and parsers.

```text id="hnl49t"
JSON over pickle
strict schemas
no arbitrary object loading
no dynamic imports from untrusted payloads
```

## 51.34 Audit Hooks and `pickle`

`pickle` can construct arbitrary Python objects and can be dangerous with untrusted input.

Audit hooks may observe some operations caused by pickle loading, but they are not a complete defense.

The primary rule remains:

```text id="pfymk6"
do not unpickle untrusted data
```

Audit hooks can help detect or restrict some dangerous behavior, but they do not transform `pickle` into a safe format.

## 51.35 Audit Hooks and Frame Access

Accessing frames and trace machinery can expose sensitive information.

Examples:

```text id="widmsa"
locals
globals
call stack
function arguments
execution flow
```

Audit events around tracing, profiling, or frame access can help hosts detect introspection.

A policy may restrict:

```text id="3n0065"
sys.settrace
sys.setprofile
frame inspection APIs
debugger attachment paths
```

Exact coverage depends on the operation.

## 51.36 Audit Hooks and Monkey Patching

Audit hooks observe events emitted by CPython and participating libraries. They do not automatically prevent all monkey patching.

Python code can still rebind names:

```python id="hg4kad"
module.function = replacement
```

unless the program or host prevents it by other means.

Audit hooks are strongest around audited runtime operations, not general object mutation.

If mutation control matters, use object capability design, restricted namespaces, separate processes, or custom wrappers.

## 51.37 Audit Hooks and `sys.audit`

`sys.audit` itself raises an audit event.

```python id="wgb8i4"
sys.audit("app.event", 1, 2, 3)
```

Hooks receive:

```text id="yzp2n4"
event = "app.event"
args = (1, 2, 3)
```

If any hook raises, `sys.audit` raises.

This means custom application audit events can also be enforceable.

Example:

```python id="z6e4hg"
def delete_user(user_id):
    sys.audit("app.user.delete", user_id)
    perform_delete(user_id)
```

A hook can block deletion.

## 51.38 Designing Custom Events

Good custom audit events are:

```text id="6qadfr"
stable
namespaced
low-cardinality in event name
clear in argument shape
emitted before the sensitive operation
documented for policy authors
```

Good:

```python id="795gre"
sys.audit("myapp.plugin.load", plugin_name, path)
```

Poor:

```python id="cjqxyv"
sys.audit(f"myapp.plugin.load.{plugin_name}")
```

Keep dynamic data in arguments, not in the event name.

## 51.39 Before vs After Events

For policy enforcement, emit before the operation.

```python id="jpn8n0"
sys.audit("myapp.file.delete", path)
os.remove(path)
```

If a hook raises, deletion does not happen.

For logging completion, use a separate after-event if useful.

```python id="dgn2cq"
sys.audit("myapp.file.delete", path)
os.remove(path)
sys.audit("myapp.file.deleted", path)
```

Most security checks should occur before the effect.

## 51.40 Audit Hook Safety Rules

Hook code should:

```text id="9avbe5"
avoid imports
avoid complex logging
avoid acquiring locks that may already be held
avoid calling untrusted code
avoid broad exceptions
filter early
keep argument inspection simple
fail closed only for precise policies
be tested in subprocesses
```

Hook code runs inside sensitive runtime paths. Treat it like signal handler or allocator-adjacent code: small, predictable, and conservative.

## 51.41 CPython Internals Mental Model

At the C level, an audited operation calls into CPython’s audit machinery.

Conceptually:

```text id="dcwg56"
operation wants to proceed
    call PySys_Audit(event, format, ...)
        build argument tuple
        call registered native hooks
        call registered Python hooks
        if hook raises, propagate exception
    continue operation if no hook blocked it
```

At the Python level:

```text id="8slx3x"
sys.audit(event, *args)
    calls registered hooks
    raises if a hook raises
```

The operation decides where to place the audit call. For enforcement, it should be placed before the sensitive action.

## 51.42 Relationship to Tracing and Profiling

Audit hooks differ from tracing and profiling.

| Mechanism | Purpose |
|---|---|
| Audit hooks | Observe security-sensitive runtime events |
| Tracing | Observe line execution, calls, returns, exceptions |
| Profiling | Observe function calls and returns with lower detail |
| Logging | Application-defined messages |
| Monitoring | Broader operational telemetry |

Audit hooks are event-oriented. They are not a general instruction-by-instruction tracing system.

## 51.43 Relationship to Import Hooks

Import hooks customize how modules are found and loaded.

Audit hooks observe that import-related events occur.

They solve different problems.

```text id="m4b73w"
import hook:
    changes import behavior

audit hook:
    observes or blocks audited import behavior
```

A custom importer should emit audit events for sensitive behavior, especially if it loads code from unusual locations.

## 51.44 Relationship to Sandboxing

Sandboxing requires controlling resources.

Audit hooks can participate in sandbox-like policy enforcement, but they do not provide complete isolation.

A real sandbox usually needs:

```text id="f303el"
process boundary
filesystem restrictions
network restrictions
resource limits
native code restrictions
environment isolation
limited IPC
strict input/output protocol
```

Audit hooks can add visibility and deny some Python-level operations inside that sandboxed process.

## 51.45 Common Bugs

| Bug | Cause | Fix |
|---|---|---|
| Hook misses early events | Hook installed too late | Install native hook before initialization |
| Hook recurses | Hook logs or imports | Keep hook minimal, add guard |
| Program breaks on import | File/import policy too broad | Allow runtime and stdlib paths |
| Policy bypassed by extension | Native code bypasses Python API | Use OS isolation, block native loading |
| Test pollution | Hook cannot be removed | Run audit-hook tests in subprocess |
| Slow runtime | Hook handles every event heavily | Filter early and avoid expensive work |
| Fragile argument parsing | Assumes wrong event shape | Check event docs and unpack defensively |

## 51.46 Minimal Audit Policy Example

```python id="qmk03e"
import os
import sys

workspace = os.path.realpath("workspace")

def is_inside_workspace(path):
    try:
        full = os.path.realpath(path)
    except TypeError:
        return True

    return full == workspace or full.startswith(workspace + os.sep)

def audit_policy(event, args):
    if event == "open":
        path = args[0] if args else None
        if isinstance(path, str) and not is_inside_workspace(path):
            raise PermissionError(path)

    elif event == "subprocess.Popen":
        raise PermissionError("subprocesses are disabled")

    elif event.startswith("ctypes."):
        raise PermissionError("ctypes is disabled")

sys.addaudithook(audit_policy)
```

This demonstrates the structure of a policy hook:

```text id="dewr2r"
filter event
extract relevant arguments
decide allow or deny
raise to block
return to allow
```

It is not a complete sandbox.

## 51.47 Minimal Custom Audit Events

Application code can expose policy points.

```python id="z91qbx"
import sys

def load_plugin(name, path):
    sys.audit("app.plugin.load", name, path)

    module = load_plugin_from_path(name, path)

    sys.audit("app.plugin.loaded", name, path)
    return module
```

A host can block plugin loading:

```python id="kn2f3c"
def hook(event, args):
    if event == "app.plugin.load":
        name, path = args
        if name not in allowed_plugins:
            raise PermissionError(name)
```

This makes application-level sensitive operations visible to the same audit system as CPython-level operations.

## 51.48 Design Rules

Install hooks as early as possible.

Use native hooks for embedding or security-sensitive hosts.

Filter by exact event names or narrow prefixes.

Raise exceptions only for precise policy violations.

Keep hook code small.

Avoid imports, complex logging, and lock-heavy behavior inside hooks.

Treat audit hooks as observability and policy hooks, not as standalone isolation.

Emit custom audit events before application-level sensitive operations.

Run tests involving global hooks in subprocesses.

## 51.49 Minimal Mental Model

Use this model:

```text id="3h9t1k"
CPython emits audit events around sensitive operations.

Each event has a name and argument tuple.

Registered hooks receive the event.

If a hook returns normally, the operation continues.

If a hook raises, the operation usually fails.

Python-level hooks are process-local and cannot be removed.

Native hooks can be installed earlier by embedding hosts.

Audit hooks improve visibility and policy enforcement, but they are not a complete sandbox.
```

## 51.50 Key Points

Audit hooks let CPython and applications report sensitive runtime events.

Use `sys.addaudithook` to register Python-level hooks.

Use `sys.audit` to emit custom events.

Hooks receive an event name and argument tuple.

Hooks can block operations by raising exceptions.

Audit hooks are useful for embedding, monitoring, policy enforcement, testing, and security visibility.

They do not replace operating system isolation.

Hook code must be small, careful, and defensive.

For strong control, combine audit hooks with process isolation, filesystem policy, network policy, native-code restrictions, and resource limits.
