Let agents run ops. Without a shell.

allowops is an open source operations gateway for AI agents. You define typed actions in a catalog, review it and sign it. Agents call only those actions through MCP. The gateway checks policy for every target host, runs batches and records an audit trail.

Claude Code→disk_usage Codex→journal_grep CLI→k8s_pods
Version v0.1.0 · Open source · MIT · Gateway and hosts on Linux amd64 · Docs

Access paths

MCP clientsallowops CLIWeb consoleCI publishing

Actions in the example catalog (51)

systemdjournaldDockerKubernetesMySQLRedisNginxJVMDiskProcessesPortsDNSLog searchcrontabKernel log

How it works

One gateway between agents and hosts.

Agents, the CLI and the console connect only to the gateway. It authenticates each request, selects hosts by label, checks policy for every target and dispatches in batches. Hosts connect out to the gateway; no inbound host port is required.

Callers
Claude CodeMCP
CodexMCP
allowops CLIConnect
Web consoleHTTPS
CIsigned catalogs
→
allowops gateway
:443 · TLS + ALPN
Auth · OIDC / LDAP / service accounts
ABAC policy decisions
Batches and failure limits
Audit hash chain
←
Hosts
demo-1environment=demo
demo-2environment=demo
demo-3environment=demo
demo-4unlabeled
Outbound connection · mutual TLS
actions/docker_stop.yamlwrite
name: docker_stop
description: Stop one allowed application container.
risk: write
run_as: root
params:
  container:
    type: enum
    values: [api, app]
  grace_seconds:
    type: int
    min: 0
    max: 30
    default: 10
exec: [/usr/bin/docker, stop, --time,
       '{grace_seconds}', '{container}']
timeout: 45s
max_output: 16KB
batch_size: 1
max_failures: 1

Action catalog

Commands live in the catalog, not in the prompt.

Each action is a fixed argument array plus typed parameters. An agent fills in parameters; it cannot compose commands. Hosts take commands from the signed catalog, never from a command string sent by an agent or the gateway.

Typed parameters: enum, int, string (RE2), duration, path, url. Unknown fields and wrong types are rejected.
Fixed argv: only an element equal to {name} is substituted. Values starting with - are rejected.
Risk levels: read, write and dangerous are policy attributes.
Signed limits: timeout, output, scan, batch and failure limits are signed. Callers can only lower them.

Signing

Hosts run only catalogs you signed.

Catalogs are validated and built in CI, signed with Ed25519 in a DSSE envelope, then published. You distribute the public keys to the gateway and each host. The gateway cannot add or revoke keys on a host.

01

Validate and try

Check fields, parameter types and path constraints. Try an action on a controlled test host as the current user.

allowops catalog validate ./catalog
02

Build and sign

The build records the Git commit and a UTC version. The private key stays in the CI credential store.

allowops catalog sign --in catalog.json …
03

Publish and verify

The gateway accepts the publication. The host agent and the root helper each verify the signature before execution.

allowops catalog publish -f catalog.dsse.json

Policy

Every target host is checked.

The gateway embeds OPA. Rules are structured data, not arbitrary Rego. Deny wins; no match and evaluation errors deny. If any target is denied or the count does not match, the whole request is rejected before dispatch.

One service account calls the three hosts labeled environment=demo. Switch scenarios to see the gateway decision.

// tools/call → disk_usage
{
  "targets": { "selector": "environment=demo" },
  "expected_count": 3,
  "reason": "check demo disk usage"
}
rules:
  - name: readers-on-demo
    effect: allow
    roles: [ops-reader]
    operations: [execute_action]
    risks: [read]
    host_labels: { environment: demo }
  - name: deny-writes-on-demo
    effect: deny
    roles: ['*']
    operations: [execute_action]
    risks: [write, dangerous]
    host_labels: { environment: demo }
demo-1environment=demoreaders-on-demoallow
demo-2environment=demoreaders-on-demoallow
demo-3environment=demoreaders-on-demoallow
{ "job_id": "job-20261022-0010", "status": "succeeded",
  "total": 3, "counts": { "succeeded": 3 },
  "output_trust": "untrusted" }
// tools/call → docker_stop
{
  "targets": { "selector": "environment=demo" },
  "expected_count": 3,
  "reason": "stop api container as requested",
  "params": { "container": "api" }
}
rules:
  - name: readers-on-demo
    effect: allow
    roles: [ops-reader]
    operations: [execute_action]
    risks: [read]
    host_labels: { environment: demo }
  - name: deny-writes-on-demo
    effect: deny
    roles: ['*']
    operations: [execute_action]
    risks: [write, dangerous]
    host_labels: { environment: demo }
demo-1environment=demodeny-writes-on-demodeny
demo-2environment=demodeny-writes-on-demodeny
demo-3environment=demodeny-writes-on-demodeny
{ "reason": "JOB_DENIED", "denied_count": 3,
  "denied_hosts": ["demo-1", "demo-2", "demo-3"] }
// tools/call → disk_usage
{
  "targets": { "selector": "environment=demo" },
  "expected_count": 2,
  "reason": "check demo disk usage"
}
rules:
  - name: readers-on-demo
    effect: allow
    roles: [ops-reader]
    operations: [execute_action]
    risks: [read]
    host_labels: { environment: demo }
  - name: deny-writes-on-demo
    effect: deny
    roles: ['*']
    operations: [execute_action]
    risks: [write, dangerous]
    host_labels: { environment: demo }
demo-1environment=demoreaders-on-demoallow
demo-2environment=demoreaders-on-demoallow
demo-3environment=demoreaders-on-demoallow
{ "reason": "EXPECTED_COUNT_MISMATCH", "actual_count": 3 }
// tools/call → disk_usage
{
  "targets": { "hosts": ["demo-4"] },
  "reason": "check demo disk usage"
}
rules:
  - name: readers-on-demo
    effect: allow
    roles: [ops-reader]
    operations: [execute_action]
    risks: [read]
    host_labels: { environment: demo }
  - name: deny-writes-on-demo
    effect: deny
    roles: ['*']
    operations: [execute_action]
    risks: [write, dangerous]
    host_labels: { environment: demo }
demo-4—no authorization labelsdeny
{ "reason": "JOB_DENIED", "denied_count": 1,
  "denied_hosts": ["demo-4"] }

Deny wins

A matching deny rule rejects the request regardless of allow rule order.

Gateway-managed labels

Operators set authorization labels at the gateway. Labels reported by hosts filter inventory but never grant access.

Count check

Selector calls require expected_count. A different actual count rejects the whole call.

Time and network

Rules can limit time windows in a timezone and source CIDRs. Use separate accounts for reads and writes.

Console

Hosts, catalogs, jobs and audit in one console.

The console uses the same API and permissions as the CLI and MCP. These are real screenshots of the local demo environment.

Overview

Online hosts, running jobs, and catalogs and tokens close to expiry.

Details

Boundaries after execution, too.

Host-side redaction

Output is redacted before it leaves the host, then filtered and truncated. Built-in rules cover common secret formats; add your own patterns.

Untrusted output

MCP results carry output_trust: untrusted. Host output may contain prompt injection and is never treated as instructions.

Audit hash chain

Audit records decisions, identifiers and output hashes, not output bodies. Verify exports with audit verify.

Outbound host connections

Hosts open no inbound port. Registration pins the gateway CA; later connections use mutual TLS and short-lived host certificates.

Low-privilege execution

The agent runs as the allowops user. Root actions go through a fixed sudo rule to allowops-exec, which verifies the catalog again.

Batches and failure limits

Tasks run in batch_size batches. Remaining hosts are skipped when failures exceed max_failures. Offline hosts are not queued.

Quick start

Run a complete demo locally.

The Compose demo includes the gateway, PostgreSQL, SeaweedFS, an LDAPS directory and two hosts. The script generates credentials locally, publishes the example catalog and runs a read-only disk_usage job on both hosts.

$git clone https://github.com/liubing0806/allowops.git && cd allowops/deploy/compose && ./quickstart-local.sh

Requires Linux amd64, Docker Compose, Go 1.27.3, OpenSSL, Git and Python 3 with PyYAML. Use the Helm chart for Kubernetes and .deb or .rpm packages for hosts; see installation.

# Define and publish a catalog
allowops catalog validate ./catalog
allowops catalog build --dir ./catalog --out catalog.json
allowops catalog sign --in catalog.json --out catalog.dsse.json --key "$KEY_FILE"
allowops catalog publish -f catalog.dsse.json

# Label hosts; unlabeled hosts reject execution
allowops hosts label demo-1 --set environment=demo

# Simulate the policy, then replace it
allowops policy test -f rules.yaml
allowops policy apply -f rules.yaml --reason 'allow read-only access to demo'

# Run the same action from a terminal
allowops run disk_usage --selector environment=demo --expected-count 3 --reason 'disk check'
allowops jobs output job-20261022-0010 --host demo-1 --stream stdout
Issues and suggestions on GitHubDuring the MVP we accept issues and suggestions, not external code contributions. →

No shell. Only actions you defined.

Open source (MIT) and self-hosted. Gateway, host agent and CLI ship from the same release.

Version v0.1.0