This document captures the CLI surface for OAG.
Start the OAG proxy server. Enforces egress policy, materializes secrets, and records audit events for all HTTP and HTTPS traffic.
Example:
oag run --policy policy.yaml --port 8080 --log audit.jsonlWith an agent command:
oag run --policy policy.yaml -- <agent-command>Config-dir form:
oag run --config-dir ./config --port 8080With admin server for health/metrics (see observability.md):
oag run --policy policy.yaml --admin-port 9090With policy hot-reload (see configuration.md):
oag run --policy policy.yaml --watch --log audit.jsonlEvaluate a single request against a policy without running an agent.
Example:
oag explain --policy policy.yaml --request "POST https://api.openai.com/v1/chat/completions"Machine-readable output:
oag explain --policy policy.yaml --request "POST https://api.openai.com/v1/chat/completions" --jsonVerbose machine-readable output (includes normalized request tuple):
oag explain --policy policy.yaml --request "POST https://api.openai.com/v1/chat/completions" --json --verboseBundle verification flags:
oag explain --policy policy.bundle.json --policy-public-key ./keys/policy-public.pem --policy-require-signature --request "POST https://api.openai.com/v1/chat/completions"Validate policy files and runtime configuration.
Example:
oag doctor --policy policy.yamlConfig-dir form:
oag doctor --config-dir ./configMachine-readable output:
oag doctor --policy policy.yaml --jsonVerbose machine-readable output (includes effective config):
oag doctor --policy policy.yaml --json --verboseIf the policy path points to a bundle, doctor --json --verbose includes a bundle object with signature metadata and verification status.
Policy test harness for CI verification.
Example:
oag test policy.yaml --cases cases.yamlConfig-dir form:
oag test --config-dir ./config --cases cases.yamlMachine-readable output:
oag test policy.yaml --cases cases.yaml --jsonVerbose machine-readable output (includes per-case decisions):
oag test policy.yaml --cases cases.yaml --json --verboseBundle verification flags:
oag test --policy policy.bundle.json --policy-public-key ./keys/policy-public.pem --policy-require-signature --cases cases.yamlEnable runtime diagnostic output to stderr. Emits timestamped lines for each request showing policy decisions, upstream connections, secret materialization, and response details.
Example:
oag run --policy policy.yaml --verbose -- <agent-command>Sample output:
[2026-02-23T12:00:00Z] starting oag 0.1.0 on 0.0.0.0:8080
[2026-02-23T12:00:00Z] policy loaded hash=abc123...
[2026-02-23T12:00:01Z] http POST https://api.openai.com:443/v1/chat/completions
[2026-02-23T12:00:01Z] policy action=allow reason=allowed_by_rule rule=allow-openai
[2026-02-23T12:00:01Z] secret materialization secrets=1
[2026-02-23T12:00:01Z] upstream connected api.openai.com:443
[2026-02-23T12:00:02Z] response status=200 bytes_in=1234 duration_ms=850
Observe policy violations without blocking requests. Note: dry-run still evaluates raw IP and DNS resolution checks but only logs decisions.
Example:
oag run --policy policy.yaml --dry-run -- <agent-command>Deny raw IPv4/IPv6 literal destinations before connect.
Example:
oag run --policy policy.yaml --block-ip-literals -- <agent-command>Re-evaluate HTTP redirect targets and block denied hops.
Example:
oag run --policy policy.yaml --enforce-redirect-policy -- <agent-command>Deny requests when DNS resolution yields private/special-purpose addresses.
Example:
oag run --policy policy.yaml --block-private-resolved-ips -- <agent-command>Set upstream connect/read socket timeouts (milliseconds).
Example:
oag run --policy policy.yaml --connect-timeout-ms 5000 --read-timeout-ms 30000 -- <agent-command>Select the secret provider backend. Supported values are env (default), file, and oauth2.
Example (env provider, default):
oag run --policy policy.yaml --secret-provider env -- <agent-command>Override the environment variable prefix (default OAG_SECRET_):
oag run --policy policy.yaml --secret-prefix OAG_SECRET_ -- <agent-command>Example (file provider):
oag run --policy policy.yaml --secret-provider file --secret-dir ./secrets -- <agent-command>Require signed policy bundles and verify with a public key:
oag run --policy policy.bundle.json --policy-public-key ./keys/policy-public.pem --policy-require-signature -- <agent-command>Enable OpenTelemetry log export for audit events. Exporters:
none(default)otlp_httpotlp_grpcstdout
Notes:
otlp_httpandotlp_grpcrequire--otel-endpoint.stdoutignores--otel-endpointand prints log records to stdout.
Example (OTLP/HTTP):
oag run --policy policy.yaml --otel-exporter otlp_http --otel-endpoint http://localhost:4318/v1/logsExample (OTLP/HTTP with headers):
oag run --policy policy.yaml --otel-exporter otlp_http --otel-endpoint https://otel.example.com/v1/logs --otel-headers "Authorization=Bearer $TOKEN"Override exporter timeout and service name:
oag run --policy policy.yaml --otel-exporter otlp_http --otel-endpoint http://localhost:4318/v1/logs --otel-timeout-ms 5000 --otel-service-name oag-prodExample (stdout exporter):
oag run --policy policy.yaml --otel-exporter stdoutPrint the policy hash:
oag hash --policy policy.yamlJSON output:
oag hash --policy policy.yaml --jsonExample output:
{"ok":true,"policy_hash":"<sha256>","bundle":{"version":1,"created_at":"<iso8601>","policy_hash":"<sha256>","signing_algorithm":"ed25519","signing_key_id":"key-1","signature_status":"verified"}}Failure example:
{"ok":false,"error_code":"config_error","error":"policy bundle signature verification failed"}Bundle verification flags:
oag hash --policy policy.bundle.json --policy-public-key ./keys/policy-public.pem --policy-require-signatureCreate a policy bundle:
oag bundle --policy policy.yaml --out policy.bundle.jsonCreate and sign a bundle:
oag bundle --policy policy.yaml --out policy.bundle.json --sign-key ./keys/policy-private.pem --key-id policy-root-1JSON output:
oag bundle --policy policy.yaml --out policy.bundle.json --jsonJSON output schema:
{"ok": true, "bundle_path": "<path>", "policy_hash": "<sha256>", "signed": false}Verify a bundle signature:
oag verify --bundle policy.bundle.json --public-key ./keys/policy-public.pemJSON output:
oag verify --bundle policy.bundle.json --public-key ./keys/policy-public.pem --jsonExample output:
{"ok":true,"policy_hash":"<sha256>","bundle":{"version":1,"created_at":"<iso8601>","policy_hash":"<sha256>","signing_algorithm":"ed25519","signing_key_id":"key-1","signature_status":"verified"}}Evaluate a synthetic request against a policy using explicit flags. Unlike explain (which takes a URL string), simulate accepts individual --method, --host, --path, --scheme, and --port flags for precise control.
Example:
oag simulate --policy policy.yaml --method POST --host api.openai.com --path /v1/chat/completionsWith explicit scheme and port:
oag simulate --policy policy.yaml --method GET --host api.example.com --path /v1/models --scheme http --port 8080JSON output:
oag simulate --policy policy.yaml --method POST --host api.openai.com --path /v1/chat --jsonDefaults:
--scheme:https--port:443(https) or80(http)--path:/
Text output:
action=allow reason=allowed_by_rule rule=openai
request: POST https://api.openai.com:443/v1/chat
eligible_secrets: OPENAI_KEY
JSON output:
{"ok":true,"action":"allow","reason_code":"allowed_by_rule","rule_id":"openai","request":{"scheme":"https","host":"api.openai.com","port":443,"method":"POST","path":"/v1/chat"},"eligible_secrets":["OPENAI_KEY"]}When the request is denied or matches a rule without secrets, the eligible_secrets field is omitted.
Evaluate multiple requests from a YAML/JSON file:
oag simulate --policy policy.yaml --batch requests.yamlJSON output:
oag simulate --policy policy.yaml --batch requests.yaml --jsonBatch input format:
requests:
- name: openai-chat
method: POST
host: api.openai.com
path: /v1/chat/completions
- name: github-repos
method: GET
host: api.github.com
path: /repos
- method: GET
host: evil.com
path: /exfilEach request supports: method (required), host (required), path (default /), scheme (default https), port (default 443/80), name (optional label).
Text output:
openai-chat: action=allow reason=allowed_by_rule rule=openai
github-repos: action=allow reason=allowed_by_rule rule=github
GET https://evil.com:443/exfil: action=deny reason=no_match_default_deny rule=-
total=3 allow=2 deny=1
rule hits:
openai: 1
github: 1
(no rule): 1
JSON output:
{"ok":true,"total":3,"allow_count":2,"deny_count":1,"rule_hit_counts":{"(no rule)":1,"github":1,"openai":1},"results":[...]}Lint a policy file for common issues. See configuration.md for details.
oag lint --policy policy.yamlJSON output:
oag lint --policy policy.yaml --jsonExit code 1 when warnings are found.
JSON output schema:
{"ok": true, "warning_count": 0, "warnings": []}Each warning object has the following fields:
| Field | Type | Description |
|---|---|---|
code |
string | Lint code string |
message |
string | Human-readable warning message |
rule_id |
string? | ID of the related rule (nullable) |
rule_index |
int? | Index of the related rule (nullable) |
section |
string? | Policy section the warning applies to (nullable) |
Print command usage and flag summary.
Machine-readable output:
oag help --json- default policy file:
<config-dir>/policy.yaml - run mode default log path:
<config-dir>/logs/audit.jsonl(if--logis omitted) - file secret provider default secret dir:
<config-dir>/secrets(if--secret-diris omitted) - explicit
--policyand--logoverride config-dir defaults
Bundle note:
- If you use a bundle, pass it explicitly via
--policy policy.bundle.json(config-dir does not auto-detect bundles).
doctor --json:
{"ok":true,"policy_hash":"<sha256>","policy_path":"<path>"}doctor --json --verbose:
{"ok":true,"policy_hash":"<sha256>","policy_path":"<path>","effective_config":{"listen_host":"0.0.0.0","listen_port":8080,"max_threads":32,"dry_run":false,"block_ip_literals":false,"enforce_redirect_policy":false,"block_private_resolved_ips":false,"connect_timeout_ms":5000,"read_timeout_ms":30000,"secret_env_prefix":"OAG_SECRET_","secret_provider":"env","policy_require_signature":false,"otel_exporter":"none","otel_headers_keys":[],"otel_timeout_ms":10000,"otel_service_name":"oag"},"bundle":{"version":1,"created_at":"<iso8601>","policy_hash":"<sha256>","signing_algorithm":"ed25519","signing_key_id":"key-1","signature_status":"verified"}}explain --json:
{"ok":true,"action":"allow|deny","reason_code":"<code>","rule_id":"<id>|null"}explain --json --verbose:
{"ok":true,"action":"allow|deny","reason_code":"<code>","rule_id":"<id>|null","request":{"scheme":"https","host":"api.example.com","port":443,"method":"POST","path":"/v1/*"}}test --json:
{"ok":true,"total":10,"passed":10,"failed":0,"failures":[]}test --json --verbose:
{"ok":false,"total":2,"passed":1,"failed":1,"failures":["case: expected(...) actual(...)"],"cases":[{"name":"case","ok":false,"expected_action":"allow","expected_reason":"allowed_by_rule","actual_action":"deny","actual_reason":"no_match_default_deny"}]}simulate --json:
{"ok":true,"action":"allow","reason_code":"allowed_by_rule","rule_id":"openai","request":{"scheme":"https","host":"api.openai.com","port":443,"method":"POST","path":"/v1/chat"},"eligible_secrets":["OPENAI_KEY"]}help --json:
{"commands":["run","doctor","explain","test","hash","bundle","verify","lint","simulate","diff","help"],"json_modes":["doctor","explain","test","hash","bundle","verify","lint","simulate","diff","help"]}oag run --policy policy.yaml --port 8080 --log audit.jsonl./gradlew shadowJar
java -jar oag-app/build/libs/oag-app-1.0-SNAPSHOT-all.jar run --policy policy.yaml --port 8080 --log audit.jsonl./gradlew :oag-app:run --args="run --policy policy.yaml --port 8080 --log audit.jsonl"File provider example:
./gradlew :oag-app:run --args="run --policy policy.yaml --port 8080 --log audit.jsonl --secret-provider file --secret-dir ./secrets"Provide secrets as environment variables using OAG_SECRET_<ID> when using the env provider. See getting-started.md for setup details.
Set agent and session identifiers for audit correlation:
oag run --policy policy.yaml --agent my-agent --session session-123--agent <id>: Agent identifier. Recorded in every audit event. Used for agent profile matching.--session <id>: Session identifier. Enables per-session tracking (data budgets, velocity, request counts).
Set the maximum thread pool size for request handling:
oag run --policy policy.yaml --max-threads 64--max-threads <n>: Maximum number of threads. Default: 32.
oag run --circuit-breaker-threshold / --circuit-breaker-reset-ms / --circuit-breaker-half-open-probes
Configure the per-host circuit breaker. See operations.md for details.
oag run --policy policy.yaml --circuit-breaker-threshold 5 --circuit-breaker-reset-ms 30000 --circuit-breaker-half-open-probes 2--circuit-breaker-threshold <n>: Consecutive failures before opening. Default: 5.--circuit-breaker-reset-ms <ms>: Time in OPEN state before probing. Default: 30000.--circuit-breaker-half-open-probes <n>: Successful probes required before closing. Default: 1.
Configure the graceful shutdown drain timeout. See operations.md for details.
oag run --policy policy.yaml --drain-timeout-ms 10000--drain-timeout-ms <ms>: Maximum time to wait for active connections to complete during shutdown. Default: 10000.
Enable TLS interception for CONNECT tunnels with tls_inspect: true in policy rules.
oag run --policy policy.yaml --tls-inspect --tls-ca-cert-path ./oag-ca.pem --log audit.jsonl--tls-inspect: Generate an ephemeral CA at startup and enable TLS interception for matching rules.--tls-ca-cert-path <file>: Write the CA certificate in PEM format to this path. Clients must trust this CA.
See security.md for client trust setup and detailed configuration.
Require mTLS client certificate authentication:
oag run --policy policy.yaml --mtls-ca-cert ./ca.pem --mtls-keystore ./keystore.p12 --mtls-keystore-password changeit--mtls-ca-cert <path>: CA certificate PEM file for verifying client certificates.--mtls-keystore <path>: PKCS12 keystore file containing the proxy's server certificate and key.--mtls-keystore-password <password>: Keystore password.
Require agents to cryptographically sign request headers:
oag run --policy policy.yaml --require-signed-headers --agent-signing-secret "shared-secret"--require-signed-headers: Reject requests without valid HMAC signatures.--agent-signing-secret <secret>: Shared secret for HMAC-SHA256 header verification.
Inject a unique request ID into upstream request headers and audit events.
oag run --policy policy.yaml --inject-request-id --log audit.jsonlWith a custom header name:
oag run --policy policy.yaml --inject-request-id --request-id-header X-Trace-Id --log audit.jsonl--inject-request-id: Generate a UUID-based request ID for each allowed request and inject it into upstream headers. The same ID is recorded in therequest_idfield of the audit event.--request-id-header <name>: Override the default header name (X-Request-Id). Only applies when--inject-request-idis set.
Request IDs are generated after policy evaluation and secret materialization, so denied requests do not receive request IDs. For MITM-intercepted CONNECT tunnels, each inner HTTP request gets its own request ID.
Detect abnormal request rate spikes per session:
oag run --policy policy.yaml --velocity-spike-threshold 3.0--velocity-spike-threshold <n>: Multiplier over baseline RPS before flagging. Default: 0 (disabled). Requires--session.
Enable periodic runtime integrity verification:
oag run --policy policy.yaml --integrity-check-interval-s 300--integrity-check-interval-s <n>: Seconds between integrity checks. Default: 0 (disabled). Emitsintegrity_checkaudit events.
Compare two policy files and report differences:
oag diff policy-v1.yaml policy-v2.yamlOutput:
allow added: new_api
allow removed: old_api
allow changed: openai_api
methods: [POST] -> [POST, GET]
defaults changed:
max_body_bytes: 1048576 -> 2097152
JSON output:
oag diff policy-v1.yaml policy-v2.yaml --json{
"ok": true,
"has_changes": true,
"defaults_changed": true,
"defaults_details": ["max_body_bytes: 1048576 -> 2097152"],
"rule_diffs": [
{"section": "allow", "id": "new_api", "change": "added", "details": []},
{"section": "allow", "id": "old_api", "change": "removed", "details": []},
{"section": "allow", "id": "openai_api", "change": "changed", "details": ["methods: [POST] -> [POST, GET]"]}
],
"secret_scope_diffs": []
}Both policies are loaded, validated, and normalized before comparison. Rules are matched by their id field. Rules without IDs are compared by structural equality.
Enable HTTP connection pooling for upstream connections:
oag run --policy policy.yaml --pool-max-idle 8 --pool-idle-timeout-ms 60000--pool-max-idle <n>: Maximum idle connections per upstream host:port. Set to 0 (default) to disable pooling.--pool-idle-timeout-ms <ms>: Idle timeout in milliseconds before pooled connections are evicted. Default: 60000.
When enabled, the proxy reuses upstream TCP connections for HTTP requests to the same host:port. Connections are returned to the pool after a complete response with defined framing (Content-Length or chunked Transfer-Encoding) and no Connection: close header. A background thread evicts expired connections.
Connection pooling only applies to HTTP forward proxy requests. CONNECT tunnels and MITM-intercepted connections are not pooled.
Pool metrics (oag_pool_hits_total, oag_pool_misses_total, oag_pool_evictions_total) are available on the admin metrics endpoint.
Enable audit log rotation for the JSONL log file:
oag run --policy policy.yaml --log audit.jsonl --log-max-size-mb 50 --log-max-files 5With gzip compression of rotated files:
oag run --policy policy.yaml --log audit.jsonl --log-max-size-mb 50 --log-max-files 10 --log-compress--log-max-size-mb <n>: Maximum size in megabytes before the active log file is rotated. Set to 0 (default) to disable rotation.--log-max-files <n>: Maximum number of rotated log files to retain. Default: 5. Oldest files beyond this limit are deleted on rotation.--log-compress: Gzip-compress rotated log files (.gzsuffix). Only applies when--log-max-size-mbis set.--log-rotation-interval <interval>: Time-based rotation (daily,hourly). Used alongside or instead of size-based rotation.
Rotated files are named <logfile>.1, <logfile>.2, etc. (or <logfile>.1.gz with compression). File .1 is always the most recent rotation. When a new rotation occurs, existing rotated files are shifted (.1 becomes .2, etc.) and the oldest file beyond --log-max-files is deleted.
Log rotation only applies when writing to a file (--log). Stdout logging is never rotated.
Enable the admin HTTP server:
oag run --policy policy.yaml --admin-port 9090 --admin-token mysecret --admin-allowed-ips 127.0.0.1,10.0.0.0/8--admin-port <n>: Port for the admin server. Disabled when omitted.--admin-allowed-ips <list>: Comma-separated IPs or CIDR ranges. Non-listed IPs receive 403.--admin-token <token>: Bearer token required for admin requests.--admin-reload-cooldown-ms <ms>: Minimum interval between reloads. Default: 5000.
See observability.md for endpoints.
Enable webhook notifications for operational events:
oag run --policy policy.yaml --webhook-url https://hooks.example.com/oag --webhook-events circuit_open,reload_failed --webhook-signing-secret mysecret--webhook-url <url>: Destination URL for webhook POST requests.--webhook-events <list>: Comma-separated event types to send. Available:circuit_open,reload_failed,injection_detected,credential_detected,integrity_drift,admin_denied.--webhook-timeout-ms <ms>: HTTP timeout for webhook delivery. Default: 5000.--webhook-signing-secret <secret>: HMAC-SHA256 signing key. AddsX-OAG-Signature: sha256=<hex>header.
See observability.md for payload format.
Periodically fetch policies from a remote URL:
oag run --policy /tmp/cached.yaml --policy-url https://config.example.com/policy.yaml --policy-fetch-interval-s 300--policy-url <url>: Remote URL to fetch the policy from.--policy-fetch-interval-s <n>: Fetch interval in seconds. Default: 60.
See operations.md for behavior details.
See operations.md for runtime reload triggers (admin endpoint, SIGHUP, file watcher).
| Flag | Type | Default | Description |
|---|---|---|---|
--policy <path> |
string | required | Policy file path |
--config-dir <path> |
string | — | Convention-based config directory |
--port <n> |
int | 8080 | Listen port |
--log <path> |
string | stdout | Audit log file path |
--agent <id> |
string | — | Agent identifier for audit |
--session <id> |
string | — | Session identifier for tracking |
--max-threads <n> |
int | 32 | Thread pool size |
--verbose |
flag | off | Debug output to stderr |
--dry-run |
flag | off | Log violations without blocking |
--watch |
flag | off | Hot-reload policy on file change |
--connect-timeout-ms <ms> |
int | 5000 | Upstream connect timeout |
--read-timeout-ms <ms> |
int | 30000 | Upstream read timeout |
--drain-timeout-ms <ms> |
long | 10000 | Graceful shutdown drain timeout |
--secret-provider <type> |
string | env | Secret backend (env, file, oauth2) |
--secret-dir <path> |
string | — | Secret files directory (file provider) |
--secret-prefix <prefix> |
string | OAG_SECRET_ | Env var prefix (env provider) |
--oauth2-token-url <url> |
string | — | OAuth2 token endpoint |
--oauth2-client-id <id> |
string | — | OAuth2 client ID |
--oauth2-client-secret <secret> |
string | — | OAuth2 client secret |
--oauth2-scope <scope> |
string | — | OAuth2 scope |
--policy-public-key <path> |
string | — | Ed25519 public key for bundle verification |
--policy-require-signature |
flag | off | Require signed policy bundles |
--block-ip-literals |
flag | off | Deny raw IP destinations |
--block-private-resolved-ips |
flag | off | Deny DNS resolving to private IPs |
--enforce-redirect-policy |
flag | off | Re-evaluate redirect targets |
--tls-inspect |
flag | off | Enable TLS interception |
--tls-ca-cert-path <path> |
string | — | Write ephemeral CA cert to this path |
--mtls-ca-cert <path> |
string | — | Client CA cert for mTLS |
--mtls-keystore <path> |
string | — | Server keystore for mTLS |
--mtls-keystore-password <pw> |
string | — | Keystore password |
--require-signed-headers |
flag | off | Require HMAC-signed headers |
--agent-signing-secret <secret> |
string | — | Shared HMAC signing secret |
--inject-request-id |
flag | off | Inject UUID request ID |
--request-id-header <name> |
string | X-Request-Id | Request ID header name |
--admin-port <n> |
int | — | Admin server port |
--admin-allowed-ips <list> |
string | — | Comma-separated allowed IPs/CIDRs |
--admin-token <token> |
string | — | Admin bearer token |
--admin-reload-cooldown-ms <ms> |
long | 5000 | Min interval between reloads |
--circuit-breaker-threshold <n> |
int | 5 | Failures before opening |
--circuit-breaker-reset-ms <ms> |
long | 30000 | OPEN → HALF_OPEN timeout |
--circuit-breaker-half-open-probes <n> |
int | 1 | Probes before closing |
--pool-max-idle <n> |
int | 0 | Max idle connections per host |
--pool-idle-timeout-ms <ms> |
long | 60000 | Idle connection timeout |
--log-max-size-mb <n> |
int | 0 | Max log file size before rotation |
--log-max-files <n> |
int | 5 | Max rotated log files |
--log-compress |
flag | off | Gzip rotated logs |
--log-rotation-interval <val> |
string | — | Time-based rotation (daily, hourly) |
--webhook-url <url> |
string | — | Webhook destination URL |
--webhook-events <list> |
string | — | Comma-separated event types |
--webhook-timeout-ms <ms> |
int | 5000 | Webhook HTTP timeout |
--webhook-signing-secret <secret> |
string | — | Webhook HMAC signing key |
--velocity-spike-threshold <n> |
double | 0 | RPS spike multiplier |
--policy-url <url> |
string | — | Remote policy fetch URL |
--policy-fetch-interval-s <n> |
long | 60 | Remote fetch interval |
--integrity-check-interval-s <n> |
long | 0 | Integrity check interval |
--otel-exporter <type> |
string | none | OTel exporter type |
--otel-endpoint <url> |
string | — | OTel collector endpoint |
--otel-headers <list> |
string | — | OTel headers (key=value,key=value) |
--otel-timeout-ms <ms> |
int | 10000 | OTel export timeout |
--otel-service-name <name> |
string | oag | OTel service name |
--plugin-provider <list> |
string | — | Comma-separated list of fully-qualified class names for plugin detector providers. Providers are loaded via reflection and must implement DetectorProvider. |