Skip to content

Latest commit

 

History

History
219 lines (176 loc) · 12.7 KB

File metadata and controls

219 lines (176 loc) · 12.7 KB

Security model

Guard assumes the agent can be mistaken, misaligned, or compromised. The daemon is the trusted broker. Security depends on the agent being unable to read the daemon's credentials or reach protected upstreams by another path.

Trust boundaries

The operator controls daemon startup, policy, verb and grant catalogs, secret storage, listener ACLs, and deployment isolation. The evaluator judges command or API intent inside those deterministic limits. The agent controls requests and project files.

The central bypass-prevention invariant is daemon-held credentials. SSH keys, SSH agent sockets, kubeconfigs, API tokens, and secret files belong to the daemon principal. Brokered clients receive only the local Guard endpoint and scoped Guard authority.

Guard is not a sandbox. If the agent can read the same credential, connect to the upstream directly, modify daemon policy, replace the daemon binary, or gain the daemon principal, it can bypass the broker. Operating-system isolation, network policy, tool-native RBAC, backups, and service supervision remain part of the deployment.

Deterministic controls and evaluator judgment

Hard controls run independently of LLM approval: authenticated peer identity, binary limits, request validation, credential-plan binding, protocol hard-denies, explicit operator policy, session expiry and suspension, consequence floors, immutable hold snapshots, and redaction.

The evaluator handles semantic intent and novel requests. It is a useful policy component, not a proof system. Typed verbs reduce repeated semantic decisions by turning evidenced regions into deterministic argv or API coverage. Generated coverage remains regime-stamped, bounded, and unable to override explicit operator boundaries.

Trusting the evaluator's judgment is a design position. Safe mode gives an agent broad maintenance authority so a daemon can hold wide host access and remain useful for local debugging and administration without an operator in the loop. A wrong allow is an instruction defect: the correction is the mode prompt, an operator prompt supplement, the verb catalog, or a regression case - never routine holds or a stripped-down daemon. Frequent holds train operators to approve reflexively, which is worse than a well-instructed evaluator. Holds are reserved for the genuinely irreversible tail.

The safe-mode envelope is visible, bounded, recoverable: a mutation is approvable when its complete effect is stated in the command text, its targets are enumerable from the text, and routine means undo it. Execution whose effects are defined in files, remote content, or tool state - configuration-management applies, infrastructure-as-code, chart releases, opaque scripts - fails visibility by construction and is denied toward the grant escalation path. Tools outside the evaluator's knowledge are unevaluable, not implicitly trusted; operators describe house tools through prompt supplements (--system-prompt-append) or typed verbs.

For cwd-dependent opaque carriers (ansible-playbook, terraform, helm, make, and the rest of the fixed classifier list) the carrier boundary is also a deterministic floor under consequence gating, not prompt compliance alone: a safe-mode evaluator allow of such a binary is clamped to an operator hold unless an operator-authored typed verb covers the command. An evaluator deny is never softened, and readonly and paranoid modes are unchanged.

Session overlays intentionally expand baseline evaluator or readonly coverage inside activated verb regions. This gives a short-lived agent bounded mutation authority without changing the global posture. The exact session revision and coverage snapshot bind any hold or provisional. Outside an activated region, an access-managed session is inert and the command follows baseline policy, regardless of whether the client or daemon attached the internal session token.

Execution and credential isolation

Approved commands receive the caller's canonical working directory but retain the daemon's clean environment, identity, SSH configuration, agent socket, and secret bindings. Caller startup variables and SSH credentials are not trusted inputs. Guard preserves argv, exit behavior, and tool semantics.

Secret values are resolved after authorization. Environment delivery clears the child environment first. Secret-file delivery creates a daemon-only lease for the child lifetime. Holds store names and salted value hashes. Audit and session history store secret names only. Output redaction covers exact resolved values and credential-shaped text.

Execution identity and credential delivery have different compromise bounds:

Context Intended use Compromise bound
Dedicated non-root service identity Default broker identity with daemon-owned SSH configuration, agent socket, and secret access An opaque approved child can retain the service account's authority, copy readable credentials, or create persistence available to that account. A grant TTL limits later Guard admissions but cannot undo those effects.
Root service identity Deployments that require root before an explicit identity drop A default child inherits root execution authority. Use --exec-as-caller or a dedicated non-root service when root is not an intentional part of the grant. Guard is not a sandbox for a root child.
Per-run environment or secret-file lease One approved process that needs one named credential Guard removes the file after the child lifetime and does not disclose the value through its protocol. An opaque child can still copy or use the credential while it is available, so lease expiry is exposure reduction rather than revocation of completed effects.

Guard has no general scoped SSH credential endpoint. Brokered SSH-using tools receive the service identity's configured SSH context. A narrower SSH transport requires a separately authenticated stream protocol, destination and forwarding constraints, revocation semantics, and an independent security review.

The API proxy injects the endpoint upstream credential only after the request is allowed. It strips authentication headers, redacts protocol-classified secret responses, rejects uninspectable sensitive streams, and binds rollback to the exact endpoint and credential identity. The public access workflow exports no API bearer.

Principals and admin authority

Unix sockets authenticate caller uid through peer credentials. Windows named pipes authenticate caller SID. The stock Windows DACL admits authenticated local users, then Guard isolates their authority by SID; it does not restrict the pipe to one configured client SID. On Unix, operator authority for holds, provisionals, saved grants, verbs, and detailed status is the admin bearer token: the token reaches the daemon through stdin at startup and is presented only by the root-owned operator wrapper, so a brokered child running as the daemon uid holds no operator authority. On Windows, kernel-authenticated local SYSTEM is the packaged operator principal. The installer runs operator commands in transient SYSTEM tasks, while brokered commands run as the daemon service SID. The service SID receives no operator exception. Packaged service mode requires a named pipe and rejects an admin bearer and TCP listener. A foreground Windows server can use an explicitly configured admin bearer instead and gives SYSTEM no implicit operator authority.

Windows clients request identification-level named-pipe security. The daemon can authenticate the client SID, but a process that wins the pipe name cannot impersonate a privileged Guard client.

On Unix, the local socket is private to the daemon unless an operator configures a group, in which case it is group-readable and group-writable. SQLite state and sidecars are owner-only regular files beneath private, non-symlinked state directories. Socket membership controls who may submit requests; session and uid authorization remain separate boundaries.

Loopback TCP carries execution and admin bearer tokens but no kernel-authenticated local principal. It therefore refuses consequence gating and per-principal credential injection. The execution token cannot perform admin RPCs.

Every command-access session is bound at creation to the authenticated principal that requested it, represented by a Unix uid or Windows SID. On every local path that consumes its authority, the daemon requires the requesting peer's kernel-authenticated principal to equal the session owner. A different local peer in the socket group that learns or replays a request reference or bearer is refused with a distinct session principal mismatch audit reason. The daemon operator principal retains cross-session inspection and administration; a non-owner non-operator peer sees only its own requests and sessions.

Startup rejects sessions without a verifiable owner and matching approved access-request provenance. Every active command session has a principal-bound request and an explicit use policy.

Access-managed sessions are command-only authority. Guard refuses brokered kubeconfig issuance and API-proxy resolution for them, so one-time and N-use command admissions cannot become reusable API credentials.

Holds, rollback, and autonomy

Reversible work executes immediately. Recoverable work uses a forward, verify, and revert envelope. Irreversible, uncertain, or connectivity-unsafe work holds before execution. A hold freezes the complete authority and execution snapshot; approval cannot pick up later catalog or secret changes.

A viable rollback chain enables unattended operation. Guard does not assume rollback is safe when the forward action can sever its control path. Persisted state survives restart, but startup does not fire overdue rollback commands in an unverified environment. These operations require an explicit decision.

Process lifetime

On Unix, brokered children lead dedicated process groups. A streaming client disconnect, request cancellation, daemon shutdown, or SIGTERM terminates the group. A buffered non-streaming request is daemon-owned after admission and runs to completion if its client disconnects; its bounded result remains available through the durable hold or provisional record when one exists. Choose streaming execution when disconnect means cancel, and buffered execution when completion must not depend on the client connection. A child that deliberately detaches through an external service manager or new session can outlive the request. Windows service stop and cancellation terminate tracked direct children.

Process ownership limits accidental or ordinary orphaning. It is not a kernel sandbox against a child that has authority to create an independent service.

Audit and state

The daemon emits a dedicated structured audit stream independent of ordinary diagnostic filtering. Records include principal, session fingerprint, decision source, matched coverage, consequence route, execution result, and safe secret names. Ship that stream through the service manager or logging stack.

SQLite stores durable saved grants, sessions, requests, holds, provisionals, read grants, and bounded interaction history. It is not a replacement for the audit stream. Protect both the database and catalog files from the agent principal.

Behavioral limits suspend sessions on observable denial or hold patterns. They reduce repeated abuse and evaluator spend amplification but cannot prove a multi-step trajectory is benign.

API and SSH boundaries

The API proxy mediates request-response protocols with typed parsing, bounded bodies, response inspection, and protocol-specific rollback. Named endpoints share one generic gate while retaining separate listener, policy, credential, coverage, and revert identities.

Raw SSH is a bidirectional byte stream with forwarding, subsystems, interactive shells, and nested transports. Guard brokers ordinary ssh commands by running the SSH client with daemon-held configuration and credentials. That does not make Guard an SSH transport proxy. A raw stream adapter requires its own protocol design and security review rather than being treated as a generic HTTP proxy configuration.

Practical limits

Guard can bound visible argv, typed API operations, session lifetime, fanout, credential selection, consequence, and observable behavior. It cannot infer all effects hidden in arbitrary local files or remote program behavior. An approved Ansible playbook, Helm chart, shell-capable tool, or API extension may have wider effects than its top-level invocation suggests.

Use narrow verbs and short grants for opaque file-driven tools, protocol-level mediation where request semantics are available, and native read-only identities where the upstream provides them. Keep irreversible and control-path-changing operations behind holds unless their rollback chain is independently viable.