> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beacon.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Restricted Log Access

> Make the collector the only writer of the system runtime log, so local accounts cannot rewrite it or read other users' sessions, and let a log shipper keep reading it

A system-mode endpoint keeps one runtime log for every user on the machine: `/var/log/beacon-agent/runtime.jsonl` on macOS and Linux, `%ProgramData%\Beacon\Endpoint\logs\runtime.jsonl` on Windows. Your SIEM shipper reads that file. Restricted log access is the mode to use when no ordinary account on the machine should be able to change that file or read it.

## Why the default log is shared

Agent hooks run as the person using the agent, not as root. In the default **shared** mode they append to the system log directly, so the file has to be writable by every account (`0666`). That has two consequences on a machine with more than one account, or one where a user should not be trusted with the evidence:

* Any local account can truncate, rewrite or delete events already in the log, including before your shipper has read them.
* Any local account can read every other user's prompts, command output and diffs.

## What restricted mode changes

In the **restricted** mode the root collector is the only process that writes the log. Hooks hand their events to it over a local Unix socket instead of opening the file.

```mermaid theme={null}
flowchart LR
  hooks["Agent hooks (run as each user)"] -->|"Unix socket /var/run/beacon-agent/ingest.sock"| collector["Beacon collector (root)"]
  otlp["Agents exporting OTLP"] -->|"127.0.0.1:4317 / 4318"| collector
  collector -->|"only writer"| log["runtime.jsonl (0640 root:beacon-log)"]
  log -->|"reads as a beacon-log member or root"| shipper["Log shipper (Sentinel, Splunk, Wazuh, ...)"]
```

| | Shared (default) | Restricted |
| - | - | - |
| Who writes `runtime.jsonl` | The collector and every user's hooks | The collector only |
| How hooks deliver events | Append to the file | Send to `/var/run/beacon-agent/ingest.sock` |
| Log and archives | `0666` | `0640`, owner `root`, group `beacon-log` |
| Log directory | Readable by everyone | `2750`, owner `root`, group `beacon-log` |
| Who can read the log | Every account | Root and members of `beacon-log` |
| Who can modify the log | Every account | Root only |
| User recorded on hook events | What the hook reports | The account the operating system says sent the event (`user.uid`, `user.name`) |
| Collector down or restarting | Hooks keep appending | Hooks hold events in a private spool and send them later |

The event format and every field stay the same, so a parser, DCR transform or SIEM rule built for the shared log needs no change. Nothing leaves the machine: the socket is a local file, and hooks still never touch the network.

### Delivery while the collector is down

If the collector is stopped or restarting, a hook writes its events to a spool in that user's home, `~/.beacon/endpoint/spool` (`0700` directory, `0600` files), and returns at once, so the agent is never held up. The next event that user's hooks send delivers the spooled ones first. Each spool is capped at 8 MiB; events past that are dropped and counted rather than filling the disk. [`beacon endpoint doctor`](https://docs.beacon.sh/cli/endpoint-doctor#runtime_log_access) reports a collector that is not answering on the socket and any events waiting in the caller's spool.

## Turn it on

> [!NOTE]
>
> Restricted log access applies to system-mode installs on macOS, Linux and Windows. A per-user log in `~/.beacon` is already private to its owner. On Windows, hooks use a named pipe instead of a socket and the log is closed off by its ACL instead of file modes; see [On Windows](#on-windows).

On a new system install:

```bash title="Install with restricted log access" theme={null}
sudo beacon endpoint install --system --log-access restricted
```

On an endpoint that is already installed, including one installed from the signed package, switch it in place. This is also the command to put in an MDM script policy (Jamf, Fleet) that runs after the package:

```bash title="Switch an existing endpoint to restricted" theme={null}
sudo beacon endpoint repair --system --log-access restricted
```

For package installs you can instead set `BEACON_LOG_ACCESS=restricted` in the environment of the package's install script.

Switching replaces the log and its archives with fresh copies, so a file handle another account opened while the log was shared stops reaching it. Existing events are kept.

The mode is recorded in `config.json` as `log_access` and in a root-owned `.beacon-log-access` marker beside the log. `beacon endpoint repair`, package upgrades, the Jamf `repair-hooks.sh` policy, and a reinstall after `uninstall --keep-logs` all keep it, so you set it once per endpoint.

## On Windows

Restricted mode works the same way on a Windows system install, with Windows mechanisms in place of the Unix ones.

| | Shared (default) | Restricted |
| - | - | - |
| Who writes `runtime.jsonl` | The collector (`BeaconCollector`, running as SYSTEM) and every user's hooks | The collector only |
| How hooks deliver events | Append to the file | Send to the named pipe `\\.\pipe\beacon-agent-ingest` |
| Log directory and everything in it | Users read; interactive users read and write the log files | SYSTEM and Administrators full control, read for the `beacon-log` local group, nothing for anyone else |
| Lock (`runtime.jsonl.lock`) | As the log | SYSTEM and Administrators only |
| User recorded on hook events | What the hook reports | The account Windows says sent the event: its SID in `user.uid` and `DOMAIN\name` in `user.name` |
| Collector down or restarting | Hooks keep appending | Hooks hold events in a private spool in their profile and send them later |

The pipe is created by the SYSTEM collector, refuses connections from other machines and denies network logons, and does not let ordinary accounts add an instance of their own. Hooks send only to a pipe that SYSTEM or Administrators created, so an account that takes the name first gets nothing: hooks keep their events in their spool, and the collector keeps running and serves the pipe as soon as the name is free. Hooks run by SYSTEM or by an administrator from an elevated shell write the log directly, as root does on Unix.

Run the commands from an elevated shell:

```powershell title="Switch a Windows endpoint to restricted" theme={null}
beacon endpoint repair --system --log-access restricted
```

For the MSI and `install-endpoint.ps1`, set `BEACON_LOG_ACCESS=restricted` in the machine environment before installing. An upgrade with the variable unset keeps whatever mode the endpoint has. The MSI's install step runs inside the Windows Installer service, which sees a machine variable set after the service started only once it restarts, so after installing check that `beacon endpoint status --system --json` reports `"log_access": "restricted"`, and if it does not, switch with `beacon endpoint repair --system --log-access restricted`.

Switching replaces every file in the log directory with a fresh copy and resets the security of everything in it, so a handle another account opened while the log was shared stops reaching it. Windows cannot replace a file that another process holds open without allowing deletes. If one does, the switch waits a few seconds, then fails and names the file. The log is put back to shared, so hooks keep writing. Close that process (or sign the account out) and run the command again.

Add a shipper's account to the readers group from an elevated shell, then restart the shipper:

```powershell title="Add a shipper's account to beacon-log" theme={null}
net localgroup beacon-log <account> /add
```

Shippers that run as SYSTEM, which is how Azure Monitor Agent and Elastic Agent run by default, keep working with no change. The Splunk Universal Forwarder runs as SYSTEM only in releases before 9.1; from 9.1 it installs by default as the virtual account `NT SERVICE\SplunkForwarder`, which must be added and the forwarder restarted:

```powershell title="Splunk Universal Forwarder 9.1 and later" theme={null}
net localgroup beacon-log "NT SERVICE\SplunkForwarder" /add
Restart-Service SplunkForwarder
```

To verify, run `beacon endpoint doctor --system` from an elevated shell and expect `runtime_log_access: ok` and `runtime_log_permissions: ok` (`restricted_acl`), then check the directory:

```powershell title="Check the log directory's access" theme={null}
icacls "$env:ProgramData\Beacon\Endpoint\logs"
```

It lists `NT AUTHORITY\SYSTEM`, `BUILTIN\Administrators` and `beacon-log` and nothing else.

## Let your log shipper read the log

Install creates the `beacon-log` group if it is missing and adds nobody to it. A shipper that runs as root keeps working with no change, and so does the Beacon Cloud forwarder. A shipper that runs as its own account needs to join the group, and then be restarted so it picks up the new membership:

```bash title="Add a shipper's account to beacon-log" theme={null}
# Linux
sudo usermod -aG beacon-log <account>
# macOS
sudo dseditgroup -o edit -a <account> -t user beacon-log
```

If you are not sure which account a shipper runs as, look at its process:

```bash title="Find the account a shipper runs as" theme={null}
ps -eo user,comm | grep -i -E '<shipper process name>'
```

Then confirm that account can read the log:

```bash title="Check the shipper can read the log" theme={null}
sudo -u <account> test -r /var/log/beacon-agent/runtime.jsonl && echo readable
```

### Microsoft Sentinel

**Azure Monitor Agent.** AMA tails the file as the account its agent runs under. If that is not root, add it to `beacon-log` as above and restart AMA. The DCR, transform and `BeaconRuntime_CL` table are unchanged.

**Vector and the Logs Ingestion API.** The [`beacon-sentinel` service](https://docs.beacon.sh/log-forwarding/microsoft-sentinel#vector-and-the-logs-ingestion-api) runs Vector as a `DynamicUser`, which has no fixed account to add to a group. Give the unit the group instead:

```ini title="/etc/systemd/system/beacon-sentinel.service" theme={null}
[Service]
DynamicUser=yes
SupplementaryGroups=beacon-log
```

```bash title="Apply it" theme={null}
sudo systemctl daemon-reload
sudo systemctl restart beacon-sentinel
```

Vector keeps its read position in `/var/lib/vector/beacon-sentinel`, so it resumes where it stopped and does not resend the log.

### Other shippers

**rsyslog.** When rsyslog [forwards the log to your collector](https://docs.beacon.sh/log-forwarding/rsyslog.md), it reads the file as root on RHEL, Debian and most other distributions, so nothing changes there. On Ubuntu it runs as the `syslog` user: run `sudo usermod -aG beacon-log syslog` and restart rsyslog. See [With restricted log access](https://docs.beacon.sh/log-forwarding/rsyslog.md#with-restricted-log-access).

The same rule applies to Splunk Universal Forwarder, Filebeat or Elastic Agent, the Datadog Agent, Wazuh, Rapid7, Sumo Logic and any customer-managed shipper reading the [local JSONL log](https://docs.beacon.sh/log-forwarding/local-jsonl.md): running as root, nothing changes; running as its own account, add that account to `beacon-log` and restart it. Splunk HEC and Falcon LogScale HEC are sent by the collector itself and are not affected.

## Who can do what on a restricted endpoint

| Action | Who can do it |
| - | - |
| Install, repair, uninstall, connect or disconnect the system endpoint, or change its log access mode | Root only (an elevated administrator on Windows). These commands change root-owned files and services, and refuse or fail without `sudo` or elevation |
| Append events to the log | Only the collector. Users' hooks deliver through the socket, stamped with the sending account |
| Rewrite, truncate or delete events already in the log | Root only |
| Read the log, including with `beacon endpoint dashboard`, `beacon scan`, `beacon traces`, and the session lookups in `beacon share`, `beacon handoff` and `beacon git` | Root and members of `beacon-log` |
| See whether the endpoint is restricted | Anyone, with `beacon endpoint status --system`. Without `sudo`, status says it cannot read the log rather than reporting it empty |

Add people to `beacon-log` only when they should see every user's sessions on that machine, the same as you would for a shipper.

## What restricted mode does and does not protect

Restricted mode protects the **log**: what has been recorded cannot be altered or read by other local accounts. It does not take control of each person's agent away from them.

* **A user still controls their own agent.** They can still send events of their own choosing through the socket, including ones that name another session's id, but every event is recorded under their own account, as the operating system reports it. They cannot write an event that claims to be someone else's.
* **A user can still change their own agent settings.** The hooks and OpenTelemetry settings that send an agent's activity to Beacon live in each user's home (for example `~/.claude/settings.json`), and that user can edit them, which stops capture of their future sessions. Restricted mode does not change this. Run [`beacon endpoint doctor --system`](https://docs.beacon.sh/cli/endpoint-doctor#console_user_configured) from your MDM to catch it: `console_user_configured` fails when the logged-in user's agents no longer point at the collector, and `sudo beacon endpoint user-config repair-installed --system` puts the settings back.
* **The OTLP ports are not attributed.** The collector's OTLP receivers on `127.0.0.1:4317` and `4318` still accept telemetry from any local process, as in the shared mode. Agents that export over OTLP, such as Claude Code and Codex, use them, and events that arrive that way carry the user the agent reports rather than one the operating system confirmed.
* **Root is trusted.** An administrator can read and change the log in either mode. On Windows that is SYSTEM and any elevated administrator.

## Verify

```bash title="Check the mode and the socket" theme={null}
sudo beacon endpoint doctor --system
ls -ld /var/log/beacon-agent
ls -l /var/log/beacon-agent/runtime.jsonl
```

Expect `runtime_log_access: ok` with `log_access_restricted`, the directory as `drwxr-s--- root beacon-log`, and the log as `-rw-r----- root beacon-log`. Then confirm events still arrive from an ordinary account: as that user, send one prompt in an agent that uses hooks, such as Cursor or Claude Code, and look for it:

```bash title="See which accounts recent events came from" theme={null}
sudo tail -n 20 /var/log/beacon-agent/runtime.jsonl | jq -c '{action: .event.action, user: .user.name}'
```

Hook events carry the user's own account name, stamped by the collector.

If doctor reports `ingest_socket_missing` or `ingest_socket_unresponsive`, the collector is not serving the socket and hooks are spooling: restart or repair the collector. The full list of results is under [`runtime_log_access`](https://docs.beacon.sh/cli/endpoint-doctor#runtime_log_access).

## Switch back

```bash title="Return to the shared log" theme={null}
sudo beacon endpoint repair --system --log-access shared
```

The log becomes writable by every account again at once, and hooks go back to appending to it directly. Shippers that read it as `beacon-log` members keep working.

## Related

> **[Endpoint install](https://docs.beacon.sh/cli/endpoint-install.md#restricted-log-access)**
>
> The `--log-access` flag and install behavior.

> **[Microsoft Sentinel](https://docs.beacon.sh/log-forwarding/microsoft-sentinel.md)**
>
> Forward the runtime log with Azure Monitor Agent or Vector.

> **[Endpoint operations](https://docs.beacon.sh/security/endpoint-operations.md)**
>
> Paths, permissions, and daemon behavior.

> **[Data flow and threat model](https://docs.beacon.sh/security/data-flow-threat-model.md)**
>
> Trust boundaries from collection to forwarding.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.