# How Magisk Implements the Denylist Using ProcessOnDenyList and DenyListEnforced Flags

> Discover how Magisk's denylist uses ProcessOnDenyList and DenyListEnforced flags to unmount Magisk binaries for isolated processes. Understand Magisk's security core.

- Repository: [John Wu/Magisk](https://github.com/topjohnwu/Magisk)
- Tags: internals
- Published: 2026-03-05

---

**Magisk's denylist feature relies on two Zygisk state flags—`ProcessOnDenyList` to identify specific packages and `DenyListEnforced` to indicate global activation—which combine into an `UNMOUNT_MASK` that triggers the unmounting of Magisk binaries for isolated processes.**

The denylist functionality in the [topjohnwu/Magisk](https://github.com/topjohnwu/Magisk) repository enables selective or system-wide hiding of root access through Zygisk's process state tracking. At the core of this implementation are bitwise flags defined in the native C++ core that determine whether spawned processes should have Magisk's resources stripped away. These flags interact with SQLite-backed package databases and atomic global state variables to enforce isolation policies.

## Understanding the Zygisk State Flags

Magisk defines its process state tracking in [`native/src/core/zygisk/module.hpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/zygisk/module.hpp) using an enum that maps to the public Zygisk API. Two specific flags control denylist behavior:

- **`ProcessOnDenyList`** – Indicates that the current process belongs to a package explicitly added to the denylist via the CLI
- **`DenyListEnforced`** – Signals that the global denylist toggle has been enabled by the user, affecting all subsequent processes

These flags are mapped through static assertions to ensure compatibility with the public `zygisk::StateFlag` enumeration. The implementation combines them into masks that drive runtime decisions about process isolation.

## Flag Definitions and the UNMOUNT_MASK

In [`native/src/core/zygisk/module.hpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/zygisk/module.hpp), Magisk defines two critical bitmasks that govern how these flags interact:

```cpp
// native/src/core/zygisk/module.hpp
static_assert(+ZygiskStateFlags::ProcessGrantedRoot == zygisk::StateFlag::PROCESS_GRANTED_ROOT);
static_assert(+ZygiskStateFlags::ProcessOnDenyList == zygisk::StateFlag::PROCESS_ON_DENYLIST);

enum : uint32_t {
    UNMOUNT_MASK = (+ZygiskStateFlags::ProcessOnDenyList | +ZygiskStateFlags::DenyListEnforced),
    PRIVATE_MASK = (+ZygiskStateFlags::DenyListEnforced | +ZygiskStateFlags::ProcessIsMagiskApp)
};

```

The **`UNMOUNT_MASK`** performs a bitwise OR of `ProcessOnDenyList` and `DenyListEnforced`. When Zygisk evaluates this mask against a process's flag word, a positive match triggers specialized unmount handling that strips Magisk's `su` binaries and hidden daemons from the process view. This ensures that denied applications cannot detect root access through filesystem traces.

## Runtime Flag Assignment in update_deny_flags()

The core logic for populating these flags resides in [`native/src/core/deny/utils.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/deny/utils.cpp) within the `update_deny_flags()` function:

```cpp
// native/src/core/deny/utils.cpp (lines 33-40)
void update_deny_flags(int uid, rust::Str process, uint32_t &flags) {
    if (is_deny_target(uid, { process.begin(), process.end() })) {
        flags |= +ZygiskStateFlags::ProcessOnDenyList;
    }
    if (denylist_enforced) {
        flags |= +ZygiskStateFlags::DenyListEnforced;
    }
}

```

This function operates during process spawn preparation. It performs two distinct checks:

1. **`is_deny_target()`** – Queries the internal SQLite-backed denylist database (populated via CLI commands in [`cli.cpp`](https://github.com/topjohnwu/Magisk/blob/main/cli.cpp)) to verify if the UID and process name match a denied package entry
2. **`denylist_enforced`** – Checks an atomic global boolean that reflects the current global enforcement state

If either condition evaluates true, the corresponding bit is set in the flags reference passed by Zygisk, which subsequently influences the `UNMOUNT_MASK` evaluation.

## Global Enforcement Controls

The global toggle for denylist enforcement is managed through two functions in [`native/src/core/deny/utils.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/deny/utils.cpp) that manipulate both persistent storage and in-memory state:

```cpp
// native/src/core/deny/utils.cpp
int enable_deny() {
    if (!denylist_enforced.exchange(true)) {
        LOGI("* Enable DenyList\n");
    }
    MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, true);
    return DenyResponse::OK;
}

int disable_deny() {
    if (denylist_enforced.exchange(false)) {
        LOGI("* Disable DenyList\n");
    }
    MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, false);
    return DenyResponse::OK;
}

```

These functions use atomic exchange operations on `denylist_enforced` to ensure thread-safe transitions. They persist the state using `MagiskD::set_db_setting()` with `DbEntryKey::DenylistConfig`, ensuring the setting survives daemon restarts. When enabled, **every** new process spawned by Zygisk receives the `DenyListEnforced` flag, effectively activating the denylist system-wide regardless of individual package entries.

## Impact on Process Isolation

When Zygisk prepares a process environment, it checks the `UNMOUNT_MASK` to determine whether to apply the private mount namespace:

```cpp
if (flags & UNMOUNT_MASK) {
    // Execute unmount sequences to hide Magisk su/daemons
}

```

This conditional triggers filesystem isolation that prevents the process from accessing Magisk's binary paths. The distinction between the two flags becomes critical here:

- **`ProcessOnDenyList`** – Isolates only specific packages added to the database, allowing granular control for banking or streaming apps
- **`DenyListEnforced`** – Applies the isolation logic universally, effectively disabling Magisk's root capabilities for the entire system while maintaining the framework's operational state

## Practical Implementation Examples

### Adding a Package to the Denylist

Use the Magisk CLI to register specific applications for isolation:

```bash

# Add com.example.banking to the denylist

magisk --denylist add com.example.banking

```

This command sends a `DenyRequest::ADD` message processed by [`native/src/core/deny/cli.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/deny/cli.cpp), storing the package in the SQLite database. Subsequent launches trigger `is_deny_target()` to set `ProcessOnDenyList`.

### Enabling Global Enforcement

Activate system-wide denylist behavior:

```bash
magisk --denylist enable

```

This invokes `enable_deny()`, setting `denylist_enforced` to `true` and persisting the configuration via `DbEntryKey::DenylistConfig`.

### Checking Flags Within a Zygisk Module

Module developers can inspect these flags to adjust behavior:

```cpp
uint32_t flags = ZygiskModule::getFlags();

if (flags & +ZygiskStateFlags::ProcessOnDenyList) {
    LOGI("Process is specifically denylisted");
}

if (flags & +ZygiskStateFlags::DenyListEnforced) {
    LOGI("Global denylist enforcement active");
}

```

### Manual Flag Testing

For debugging or custom implementations:

```cpp
uint32_t flags = 0;
update_deny_flags(10083, rust::Str("com.test.app"), flags);
// flags now contains ProcessOnDenyList if the package is registered

```

## Summary

- **`ProcessOnDenyList`** and **`DenyListEnforced`** are defined in [`native/src/core/zygisk/module.hpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/zygisk/module.hpp) and control process isolation in Magisk's Zygisk implementation
- The **`UNMOUNT_MASK`** combines both flags to trigger unmounting of Magisk binaries when either specific packages are targeted or global enforcement is active
- **`update_deny_flags()`** in [`native/src/core/deny/utils.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/deny/utils.cpp) evaluates SQLite database entries and the atomic `denylist_enforced` flag during process spawning
- **`enable_deny()`** and **`disable_deny()`** provide thread-safe global toggling with persistent storage via `DbEntryKey::DenylistConfig`
- These mechanisms enable both targeted hiding from specific applications and system-wide root concealment

## Frequently Asked Questions

### What is the difference between ProcessOnDenyList and DenyListEnforced?

**`ProcessOnDenyList`** indicates that the specific package running in the current process has been added to the denylist database, triggering isolation for that app only. **`DenyListEnforced`** is a global flag set when the user enables the denylist feature system-wide, causing Zygisk to treat all new processes as if they require isolation regardless of individual package status.

### How does Magisk persist the denylist enforcement state across reboots?

The `enable_deny()` and `disable_deny()` functions call `MagiskD::Get().set_db_setting()` with `DbEntryKey::DenylistConfig` to store the boolean state in Magisk's internal SQLite database. During daemon initialization, this value restores the atomic `denylist_enforced` flag, ensuring the previous enforcement state applies immediately to newly spawned processes.

### Can Zygisk modules detect if a process is being denylisted?

Yes. Modules can retrieve the current process flags using `ZygiskModule::getFlags()` and test against `+ZygiskStateFlags::ProcessOnDenyList` or `+ZygiskStateFlags::DenyListEnforced` using bitwise AND operations. This allows modules to modify their behavior when Magisk is hiding itself from the current process or when global enforcement is active.

### Where is the denylist package database stored and checked?

The package database is maintained in SQLite and checked via `is_deny_target()` in [`native/src/core/deny/utils.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/deny/utils.cpp). This function receives the process UID and name, then queries the database to determine if the package was added via the CLI commands defined in [`native/src/core/deny/cli.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/deny/cli.cpp), returning a boolean that drives the `ProcessOnDenyList` flag assignment.