Safe Read-Only Agent Access to Production Linux Hosts

Bottom line

A production agent account can be made strongly constrained and read-oriented using standard Linux, OpenSSH, systemd, AppArmor, sudoers, and AWS controls. The goal shouldn't be “arbitrary shell that's 100% harmless,” because arbitrary local execution can still harm availability or confidentiality. The goal should be: direct production introspection with bounded blast radius.

The highest-risk area is container access. Direct access to /var/run/docker.sock or membership in the docker group is effectively root-level control. Container introspection should instead be exposed through narrowly allowed sudo commands such as docker ps, docker inspect, docker logs --tail, docker top, and docker stats --no-stream.

Recommended security model

Use a dedicated account:

agent-ro

The account should have:

  • No password login
  • No sudo group membership
  • No Docker group membership
  • No LXD group membership
  • No direct Docker or Podman socket access
  • No write access to application or system paths
  • Cgroup/systemd resource limits
  • AppArmor confinement
  • SSH forwarding disabled
  • Audited access

This gives the agent direct production visibility while avoiding broad production control.

Why normal read-only shell access is not enough

A normal unprivileged Linux user may be unable to modify root-owned files, but it can often still:

  • Consume CPU
  • Consume memory
  • Create many processes
  • Perform expensive disk reads
  • Scan internal networks
  • Read secrets available to its UID or groups
  • Access logs or process environments containing credentials

So read-only access must cover more than file permissions. It must constrain:

filesystem writes
process count
CPU
memory
network egress
privileged groups
container runtime access
log and secret visibility

Core controls

1. OpenSSH restrictions

For the production read-only user, disable pivot features:

Match User agent-ro
    AllowTcpForwarding no
    AllowAgentForwarding no
    X11Forwarding no
    PermitTunnel no
    GatewayPorts no
    PermitUserEnvironment no
    PermitTTY yes
    PasswordAuthentication no

If the agent doesn't need an interactive TTY, disable it too:

PermitTTY no

2. No privileged groups

Don't add agent-ro to:

sudo
docker
lxd
adm
root
systemd-journal

Only add log-reading groups if the confidentiality risk is accepted.

3. systemd/cgroup limits

Apply limits to the user slice:

# /etc/systemd/system/user-<uid>.slice.d/agent-ro.conf
[Slice]
CPUQuota=20%
MemoryMax=512M
TasksMax=128
IOWeight=10

Reload systemd:

sudo systemctl daemon-reload

Also consider PAM limits:

# /etc/security/limits.d/agent-ro.conf
agent-ro hard nproc 128
agent-ro hard nofile 1024
agent-ro hard core 0

4. AppArmor confinement

Use AppArmor to enforce mandatory access control beyond Unix permissions.

Desired policy shape:

allow read:
  /etc/**
  /usr/**
  /lib/**
  /proc/**
  /sys/**
  selected /var/log/**
  selected application config paths

deny write:
  /**

deny read:
  /etc/shadow
  /root/**
  /home/*/.ssh/**
  **/*.pem
  **/*.key
  **/.env
  credential files

deny network:
  outbound network except SSH session itself, if possible

AppArmor should be developed in complain mode first, then moved to enforce mode after testing.

Container introspection

Do not grant Docker socket access

Don't allow:

agent-ro in docker group
agent-ro access to /var/run/docker.sock
agent-ro access to /run/podman/podman.sock
agent-ro access to /run/user/*/podman/podman.sock

Docker daemon access isn't read-only by default. A user who can access the Docker socket can usually create privileged containers, mount host filesystems, exec into containers, or otherwise gain host-level control.

Use sudoers allowlists instead

The safer system-provided pattern is:

agent-ro cannot run docker directly
agent-ro can run selected docker commands through sudo
root accesses docker.sock
sudoers decides which commands are allowed

Example:

Defaults:agent-ro !authenticate
Defaults:agent-ro env_reset
Defaults:agent-ro log_output

Cmnd_Alias DOCKER_RO = \
    /usr/bin/docker ps *, \
    /usr/bin/docker inspect *, \
    /usr/bin/docker logs --tail 5000 *, \
    /usr/bin/docker top *, \
    /usr/bin/docker stats --no-stream *, \
    /usr/bin/docker port *, \
    /usr/bin/docker images *, \
    /usr/bin/docker network inspect *, \
    /usr/bin/docker volume inspect *

agent-ro ALL=(root) NOPASSWD: DOCKER_RO

Podman can follow the same pattern:

Cmnd_Alias PODMAN_RO = \
    /usr/bin/podman ps *, \
    /usr/bin/podman inspect *, \
    /usr/bin/podman logs --tail 5000 *, \
    /usr/bin/podman top *, \
    /usr/bin/podman stats --no-stream *, \
    /usr/bin/podman port *, \
    /usr/bin/podman images *, \
    /usr/bin/podman network inspect *, \
    /usr/bin/podman volume inspect *

agent-ro ALL=(root) NOPASSWD: PODMAN_RO

Commands to avoid

Don't allow:

docker run
docker exec
docker cp
docker build
docker pull
docker push
docker compose
docker start
docker stop
docker restart
docker kill
docker rm
docker system prune
docker volume rm
docker network create
docker network rm

podman run
podman exec
podman cp
podman mount
podman unshare
podman start
podman stop
podman restart
podman kill
podman rm

docker exec and podman exec are especially important to block. They aren't read-only introspection; they're arbitrary command execution inside a production container.

AWS access path

For EC2 production access, prefer AWS Systems Manager Session Manager over inbound SSH when possible.

Benefits:

  • No public SSH port required
  • IAM-based access control
  • Centralized access revocation
  • CloudTrail session-start/session-end events
  • Optional CloudWatch/S3 session logs

Important caveat: AWS states that session logging isn't available for Session Manager sessions that tunnel SSH or port forwarding, because Session Manager only carries encrypted tunnel data in those cases.

What this design allows

The agent can inspect:

  • OS version and kernel
  • Packages
  • Services
  • Systemd unit state
  • Logs within policy
  • Network listeners
  • Routes
  • Disk usage
  • Mounts
  • Process lists
  • Container metadata
  • Container logs
  • Container resource usage
  • Container port mappings
  • Container image names and IDs
  • Docker/Podman networks and volumes metadata

What this design intentionally blocks

The agent can't:

  • Become root
  • Restart services
  • Modify config
  • Write application files
  • Mutate containers
  • Exec into containers
  • Create new containers
  • Access raw Docker control socket
  • Use SSH forwarding as a pivot
  • Freely scan internal networks
  • Consume unlimited host resources

Residual risks

This model is safer, not magical.

Remaining risks include:

  • Secrets exposed in readable logs or configs
  • Sensitive environment variables visible through allowed commands
  • Sudoers wildcard mistakes
  • AppArmor profile gaps
  • Expensive but allowed read operations
  • Docker inspect exposing secrets in env or labels
  • Logs containing credentials or customer data

So the design should be paired with:

  • Log redaction
  • Secret hygiene
  • Least-privilege file ACLs
  • Short-lived access
  • Audit logs
  • Budget/resource alarms
  • Periodic access review

Practical recommendation

For direct production access without a custom gateway, the best pattern is:

restricted production user
+ OpenSSH restrictions
+ AppArmor confinement
+ systemd/cgroup limits
+ no Docker socket access
+ sudoers allowlist for container read commands
+ AWS/IAM audit trail

This gives an AI agent meaningful production introspection while avoiding the most dangerous mistake: treating Docker or Podman control-plane access as read-only.

Sources