Documentation
¶
Overview ¶
Package daemon implements the proxy daemon's management API.
Backwards Compatibility ¶
The daemon is a long-lived process that may run a different binary version than the CLI or the moat-run process. This happens during development (rebuilding moat while runs are active) and during upgrades.
To keep old and new versions interoperable, the daemon API follows these rules:
- Additive only: new fields in request/response structs are fine (encoding/json ignores unknown fields). Never remove or rename fields.
- New endpoints are fine: old clients won't call them. New clients must handle 404 gracefully when talking to an older daemon.
- Never change the semantics of existing fields.
When adding new API surface, consider: "will a CLI built today still work if the daemon is an older binary?" and vice versa.
Package daemon implements the proxy daemon for multi-run credential injection.
Index ¶
- Constants
- Variables
- func Branding() proxy.Branding
- func GCOrphanWorkspaceVolumes(ctx context.Context)
- func RaiseFileLimit() (oldSoft, newSoft uint64, err error)
- func RemoveLockFile(dir string)
- func RestoreRuns(ctx context.Context, registry *Registry, runs []PersistedRun) int
- func RestoreRunsWithSerial(ctx context.Context, registry *Registry, runs []PersistedRun, ...) int
- func StartTokenRefresh(ctx context.Context, rc *RunContext, grants []string)
- func WriteLockFile(dir string, info LockInfo) error
- type AWSConfig
- type Client
- func (c *Client) Health(ctx context.Context) (*HealthResponse, error)
- func (c *Client) ListRuns(ctx context.Context) ([]RunInfo, error)
- func (c *Client) RegisterRoutes(ctx context.Context, agent string, services map[string]string) error
- func (c *Client) RegisterRun(ctx context.Context, regReq RegisterRequest) (*RegisterResponse, error)
- func (c *Client) Shutdown(ctx context.Context) error
- func (c *Client) UnregisterRoutes(ctx context.Context, agent string) error
- func (c *Client) UnregisterRun(ctx context.Context, token string) error
- func (c *Client) UpdateRun(ctx context.Context, token, containerID string) error
- type CommandContainerChecker
- type ContainerChecker
- type CredentialEntry
- type CredentialSpec
- type ExtraHeaderEntry
- type ExtraHeaderSpec
- type HealthResponse
- type IdleTimer
- type LivenessChecker
- type LockInfo
- type PersistedRun
- type PolicyRuleSetSpec
- type RegisterRequest
- type RegisterResponse
- type Registry
- func (r *Registry) Count() int
- func (r *Registry) List() []*RunContext
- func (r *Registry) Lookup(token string) (*RunContext, bool)
- func (r *Registry) Register(rc *RunContext) string
- func (r *Registry) RegisterWithToken(rc *RunContext, token string)
- func (r *Registry) Unregister(token string)
- func (r *Registry) UnregisterAndGet(token string) (*RunContext, bool)
- func (r *Registry) UnregisterAndGetWithCount(token string) (rc *RunContext, found bool, remaining int)
- func (r *Registry) UpdateContainerID(token, containerID string) bool
- type RemoveHeaderSpec
- type RouteRegistration
- type RunContext
- func (rc *RunContext) AddExtraHeader(host, headerName, headerValue string)
- func (rc *RunContext) AddResponseTransformer(host string, transformer credential.ResponseTransformer)
- func (rc *RunContext) CancelRefresh()
- func (rc *RunContext) Close()
- func (rc *RunContext) CredentialBundleGrants() []string
- func (rc *RunContext) GetContainerID() string
- func (rc *RunContext) GetCredential(host string) (CredentialEntry, bool)
- func (rc *RunContext) GetCredentials(host string) []CredentialEntry
- func (rc *RunContext) GetExtraHeaders(host string) []ExtraHeaderEntry
- func (rc *RunContext) GetRemoveHeaders(host string) []string
- func (rc *RunContext) GetResponseTransformers(host string) []credential.ResponseTransformer
- func (rc *RunContext) GetTokenSubstitution(host string) (TokenSubstitutionEntry, bool)
- func (rc *RunContext) RemoveRequestHeader(host, headerName string)
- func (rc *RunContext) SetAWSHandler(h http.Handler)
- func (rc *RunContext) SetContainerID(id string)
- func (rc *RunContext) SetCredential(host, value string)
- func (rc *RunContext) SetCredentialBundle(host string, bundle credential.Bundle)
- func (rc *RunContext) SetCredentialHeader(host, headerName, headerValue string)
- func (rc *RunContext) SetCredentialWithGrant(host, headerName, headerValue, grant string)
- func (rc *RunContext) SetRefreshCancel(cancel context.CancelFunc)
- func (rc *RunContext) SetTokenSubstitution(host, placeholder, realToken string)
- func (rc *RunContext) ToProxyContextData() *proxy.RunContextData
- type RunInfo
- type RunPersister
- type SerialDeviceSpec
- type Server
- func (s *Server) ListenSerialPinned(rc *RunContext, specs []SerialDeviceSpec, bindAddr string, ...) (map[string]string, error)
- func (s *Server) Registry() *Registry
- func (s *Server) SetOnEmpty(fn func())
- func (s *Server) SetOnRegister(fn func())
- func (s *Server) SetOnShutdown(fn func())
- func (s *Server) SetOnUnregister(fn func(runID string))
- func (s *Server) SetPersister(p *RunPersister)
- func (s *Server) SetProxyPort(port int)
- func (s *Server) SetRoutes(rt *routing.RouteTable)
- func (s *Server) SetSerialBroker(b *serialbroker.Broker)
- func (s *Server) Start() error
- func (s *Server) Stop(ctx context.Context) error
- type TokenSubstitutionEntry
- type TokenSubstitutionSpec
- type TransformerSpec
- type UpdateRunRequest
Constants ¶
const ( CapKeepPolicy = "keep-policy" CapKeepBodyPolicy = "keep-body-policy" CapHostGatewayV2 = "host-gateway-v2" CapCredentialRefs = "credential-refs-v1" CapCredentialBundles = "credential-bundles-v1" CapSerialDevices = "serial-devices" // CapKeepScopedBody means the daemon reads a request body only for the // requests a body rule's operation matches (keep RequiresBodyFor), rather // than for every request once any body rule exists. github.scope: // workspace needs it: its GraphQL body rule would otherwise make git // pushes over HTTPS (non-JSON bodies) fail closed. CapKeepScopedBody = "keep-scoped-body" // CapKeepPersistedPolicy means registered policies survive daemon restart. CapKeepPersistedPolicy = "keep-persisted-policy" )
Daemon capability identifiers advertised in HealthResponse.Capabilities and checked by the CLI before registering a run. These strings are part of the cross-version contract (see Backwards Compatibility above): the daemon advertises them and the CLI checks them, so both sides must use the same literal — keep them as shared constants to prevent an advertise/check typo from silently disabling a capability gate. Add, never rename or remove.
const ( CAOrganization = "Moat" CACommonName = "Moat CA" )
CA subject for the daemon's TLS-interception CA. A CA created by an earlier moat keeps the subject it was created with.
const DefaultProxyPort = 19080
DefaultProxyPort is the default port for the daemon's credential-injecting proxy. This is intentionally different from the routing proxy port (8080) to avoid conflicts on macOS where both 0.0.0.0:PORT and 127.0.0.1:PORT can coexist and Docker traffic hits the wrong listener.
const MCPStubPrefix = "moat-stub-"
MCPStubPrefix starts the placeholder credential moat writes into agent MCP configs; the proxy replaces it with the real credential.
const WorkspaceVolumePrefix = "moat-ws-"
WorkspaceVolumePrefix is the prefix moat uses for per-run workspace volume names (the name is WorkspaceVolumePrefix + runID). It is the single source of truth for the naming convention: run.WorkspaceVolumeName builds names from it, and the GC here strips it to recover run IDs. It lives in this package because internal/run imports internal/daemon (not the reverse), so the shared constant cannot live in run without an import cycle.
Variables ¶
var BuildCommit string
BuildCommit is the git commit hash of the running binary. Set by the CLI at startup so the health endpoint can report the daemon's version. This allows diagnosing version skew between daemon and CLI.
var ErrRunNotFound = errors.New("run not found")
ErrRunNotFound is returned when a run is not registered with the daemon.
Functions ¶
func Branding ¶ added in v0.9.1
Branding is the client-visible wording the daemon's proxy uses for blocked responses and relay errors. Gatekeeper's defaults are neutral; moat keeps the wording (and the MCP stub prefix) it has always used, and points the hints at moat.yaml and moat commands.
MCPStubPrefix must match the placeholder moat writes into agent MCP configs (see run.mcpStubPrefix): the relay only swaps in the real credential when the client sends a value with this prefix.
func GCOrphanWorkspaceVolumes ¶ added in v0.7.0
GCOrphanWorkspaceVolumes reclaims moat-ws-<run-id> Docker named volumes whose run has been destroyed (its run directory no longer exists). A volume is normally removed on `moat destroy`, but a crashed run or SIGKILL leaks it. This pass enumerates moat-ws-* volumes and removes those whose run id has no corresponding run directory.
It is intentionally defensive: any failure (no Docker, Apple runtime, list error, run-dir enumeration error) logs at debug/warn and returns without disturbing the daemon. The caller can safely invoke it during idle cleanup.
func RaiseFileLimit ¶ added in v0.7.0
RaiseFileLimit raises this process's RLIMIT_NOFILE soft limit toward desiredNofile, capped at the hard limit. It returns the previous soft limit and the soft limit in effect afterward (always >= oldSoft). It is a no-op (equal values) when the soft limit is already at or above the target.
A limit that cannot be raised is not an error — the process keeps its existing, already-in-effect limit and err stays nil. err is non-nil only when the current limit cannot be read. Some systems, notably macOS, reject a soft limit above kern.maxfilesperproc even when the hard limit is nominally higher; when the full target is rejected, the fallback binary-searches the highest value the system will accept between the old soft limit and the target.
func RestoreRuns ¶ added in v0.3.2
func RestoreRuns(ctx context.Context, registry *Registry, runs []PersistedRun) int
RestoreRuns re-registers persisted runs by re-resolving credentials from the encrypted credential store. Returns the number of successfully restored runs.
func RestoreRunsWithSerial ¶ added in v0.8.0
func RestoreRunsWithSerial(ctx context.Context, registry *Registry, runs []PersistedRun, listen serialRestoreListener) int
RestoreRunsWithSerial is RestoreRuns with serial-device support: a restored run's listeners are re-opened on the ports its container already holds, via the supplied listener. A run whose port is now occupied fails loudly and is skipped — not silently rebound elsewhere, where the container could not reach it.
func StartTokenRefresh ¶
func StartTokenRefresh(ctx context.Context, rc *RunContext, grants []string)
StartTokenRefresh begins a background goroutine that periodically refreshes credentials for the given run context.
func WriteLockFile ¶
WriteLockFile writes the daemon lock file.
Types ¶
type AWSConfig ¶
type AWSConfig struct {
RoleARN string `json:"role_arn"`
Region string `json:"region"`
SessionDuration time.Duration `json:"session_duration"`
ExternalID string `json:"external_id,omitempty"`
Profile string `json:"profile,omitempty"`
}
AWSConfig holds AWS credential provider configuration.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client communicates with the daemon over a Unix socket.
func EnsureRunning ¶
EnsureRunning checks if the daemon is already running and returns a client. If not running, it starts the daemon via self-exec and waits for it to be ready.
An advisory file lock serializes the check-and-spawn sequence so concurrent callers don't each spawn a separate daemon process.
func Restart ¶ added in v0.6.0
Restart atomically replaces the running daemon with a fresh one spawned from the current binary. It holds the daemon spawn lock across the entire stop→start sequence so concurrent EnsureRunning callers (e.g. health monitors) block until the new daemon is up, then observe it healthy and do nothing — closing the window where a stopped daemon could be resurrected by an old binary.
The returned bool reports whether an existing daemon was stopped (true) or nothing was running and a fresh daemon was simply started (false).
func (*Client) Health ¶
func (c *Client) Health(ctx context.Context) (*HealthResponse, error)
Health returns the daemon's health status.
func (*Client) RegisterRoutes ¶
func (c *Client) RegisterRoutes(ctx context.Context, agent string, services map[string]string) error
RegisterRoutes registers service routes for an agent.
func (*Client) RegisterRun ¶
func (c *Client) RegisterRun(ctx context.Context, regReq RegisterRequest) (*RegisterResponse, error)
RegisterRun registers a new run with the daemon.
func (*Client) UnregisterRoutes ¶
UnregisterRoutes removes service routes for an agent.
func (*Client) UnregisterRun ¶
UnregisterRun removes a run from the daemon.
type CommandContainerChecker ¶
type CommandContainerChecker struct {
// contains filtered or unexported fields
}
CommandContainerChecker checks container liveness by shelling out to the container runtime CLI. It tries Docker first, then Apple containers.
func NewCommandContainerChecker ¶
func NewCommandContainerChecker() *CommandContainerChecker
NewCommandContainerChecker creates a new container checker that uses CLI commands.
func (*CommandContainerChecker) ForgetContainer ¶ added in v0.4.0
func (c *CommandContainerChecker) ForgetContainer(id string)
ForgetContainer removes the cached runtime for a container that no longer exists.
func (*CommandContainerChecker) IsContainerRunning ¶
IsContainerRunning checks if a container is still running. Returns (true, nil) when the container is confirmed alive, (false, nil) when confirmed dead, and (false, err) when the check itself failed (e.g. transient CLI error).
type ContainerChecker ¶
type ContainerChecker interface {
IsContainerRunning(ctx context.Context, id string) (alive bool, err error)
}
ContainerChecker checks if a container is still running. Returns (true, nil) when confirmed alive, (false, nil) when confirmed dead, and (false, err) when the check failed (transient error).
type CredentialEntry ¶
type CredentialEntry struct {
Name string `json:"name"` // Header name (e.g., "Authorization", "x-api-key")
Value string `json:"value"` // Header value
Grant string `json:"grant"` // Grant name for logging
}
CredentialEntry holds a credential header for proxy injection.
type CredentialSpec ¶
type CredentialSpec struct {
Host string `json:"host"`
Header string `json:"header"`
Value string `json:"value"`
Grant string `json:"grant,omitempty"`
}
CredentialSpec describes a credential to inject for a host.
type ExtraHeaderEntry ¶
ExtraHeaderEntry holds an additional header to inject.
type ExtraHeaderSpec ¶
type ExtraHeaderSpec struct {
Host string `json:"host"`
HeaderName string `json:"header_name"`
Value string `json:"value"`
}
ExtraHeaderSpec describes an additional header to inject.
type HealthResponse ¶
type HealthResponse struct {
PID int `json:"pid"`
ProxyPort int `json:"proxy_port"`
RunCount int `json:"run_count"`
StartedAt string `json:"started_at"`
Commit string `json:"commit,omitempty"` // Git commit hash of the daemon binary
Capabilities []string `json:"capabilities,omitempty"` // Feature capabilities supported by this daemon
}
HealthResponse is returned from GET /v1/health.
type IdleTimer ¶
type IdleTimer struct {
// contains filtered or unexported fields
}
IdleTimer triggers a callback after a period of inactivity.
func NewIdleTimer ¶
NewIdleTimer creates an idle timer (does not start automatically).
type LivenessChecker ¶
type LivenessChecker struct {
// contains filtered or unexported fields
}
LivenessChecker periodically checks container liveness and cleans up dead runs.
func NewLivenessChecker ¶
func NewLivenessChecker(registry *Registry, checker ContainerChecker) *LivenessChecker
NewLivenessChecker creates a new liveness checker with 30-second default interval.
func (*LivenessChecker) CheckOnce ¶
func (lc *LivenessChecker) CheckOnce(ctx context.Context)
CheckOnce performs a single liveness check for all registered runs.
func (*LivenessChecker) Run ¶
func (lc *LivenessChecker) Run(ctx context.Context)
Run starts the periodic liveness check loop. Blocks until ctx is canceled.
func (*LivenessChecker) SetOnCleanup ¶
func (lc *LivenessChecker) SetOnCleanup(fn func(token, runID string))
SetOnCleanup sets a callback invoked when a run is cleaned up. The callback receives both the auth token and run ID.
func (*LivenessChecker) SetOnEmpty ¶
func (lc *LivenessChecker) SetOnEmpty(fn func())
SetOnEmpty sets a callback invoked when the registry becomes empty after cleanup.
func (*LivenessChecker) SetPersister ¶ added in v0.3.2
func (lc *LivenessChecker) SetPersister(p *RunPersister)
SetPersister sets the run persister for saving registry state after cleanup.
type LockInfo ¶
type LockInfo struct {
PID int `json:"pid"`
ProxyPort int `json:"proxy_port"`
SockPath string `json:"sock_path"`
StartedAt time.Time `json:"started_at"`
Commit string `json:"commit,omitempty"` // Git commit hash of the daemon binary
}
LockInfo holds information about a running daemon.
func ReadLockFile ¶
ReadLockFile reads the daemon lock file. Returns nil, nil if not found.
type PersistedRun ¶ added in v0.3.2
type PersistedRun struct {
// Older daemons did not save policy inputs. Never silently restore a
// GitHub credential when its previous restrictions are unknown.
PolicyStateKnown bool `json:"policy_state_known,omitempty"`
PolicyYAML map[string][]byte `json:"policy_yaml,omitempty"`
PolicyRuleSets []PolicyRuleSetSpec `json:"policy_rule_sets,omitempty"`
PolicyRequired bool `json:"policy_required,omitempty"`
AuthToken string `json:"auth_token"`
RunID string `json:"run_id"`
ContainerID string `json:"container_id,omitempty"`
Grants []string `json:"grants,omitempty"`
MCPServers []config.MCPServerConfig `json:"mcp_servers,omitempty"`
NetworkPolicy string `json:"network_policy,omitempty"`
NetworkAllow []string `json:"network_allow,omitempty"`
AWSConfig *AWSConfig `json:"aws_config,omitempty"`
TransformerSpecs []TransformerSpec `json:"transformer_specs,omitempty"`
CopilotGitHubAuth bool `json:"copilot_github_auth,omitempty"`
CredProfile string `json:"cred_profile,omitempty"`
// Serial state. The container's MOAT_SERIAL_*_URL froze the device's
// host:port at container create, so a daemon restart must re-open the
// listeners on the same port numbers — a fresh ephemeral port would be
// unreachable from inside the container for the rest of the run.
SerialDevices []SerialDeviceSpec `json:"serial_devices,omitempty"`
SerialBindAddr string `json:"serial_bind_addr,omitempty"`
SerialAddrs map[string]string `json:"serial_addrs,omitempty"`
// AllowedHostPorts are the run's serial-listener host ports, the only ones
// the firewall lets the container reach directly — network.host and
// base_url ports stay proxy-mediated, and a rule here would grant them raw
// egress. Restore re-populates them so a restarted daemon still enforces
// the run's network policy.
AllowedHostPorts []int `json:"allowed_host_ports,omitempty"`
}
PersistedRun is the on-disk representation of a registered run. It stores grant names (not raw credentials) so provider tokens are never written to disk. The auth token (proxy-local random string) is stored in plaintext, protected by file permissions (0600). On restore, provider credentials are re-resolved from the encrypted credential store.
type PolicyRuleSetSpec ¶ added in v0.5.0
type PolicyRuleSetSpec struct {
Scope string `json:"scope"`
Mode string `json:"mode"`
Deny []string `json:"deny"`
}
PolicyRuleSetSpec describes a programmatic policy using Keep's RuleSet builder. Used for inline deny-list policies from moat.yaml. The daemon compiles these with keep.NewRuleSet() instead of parsing YAML.
type RegisterRequest ¶
type RegisterRequest struct {
RunID string `json:"run_id"`
AuthToken string `json:"auth_token,omitempty"` // Re-registration: use existing token
Credentials []CredentialSpec `json:"credentials,omitempty"`
// CredentialRefs are grant names the daemon must resolve from the encrypted
// store. Unlike Credentials, they are safe for clients to retain and replay.
CredentialRefs []string `json:"credential_refs,omitempty"`
ExtraHeaders []ExtraHeaderSpec `json:"extra_headers,omitempty"`
RemoveHeaders []RemoveHeaderSpec `json:"remove_headers,omitempty"`
TokenSubstitutions []TokenSubstitutionSpec `json:"token_substitutions,omitempty"`
MCPServers []config.MCPServerConfig `json:"mcp_servers,omitempty"`
NetworkPolicy string `json:"network_policy,omitempty"`
NetworkAllow []string `json:"network_allow,omitempty"`
NetworkRules []netrules.HostRules `json:"network_rules,omitempty"`
Grants []string `json:"grants,omitempty"`
CopilotGitHubAuth bool `json:"copilot_github_auth,omitempty"`
AWSConfig *AWSConfig `json:"aws_config,omitempty"`
ResponseTransformers []TransformerSpec `json:"response_transformers,omitempty"`
// CredProfile is the credential profile the run was created under. The
// daemon scopes token refresh to it. Additive/optional: an older CLI omits
// it and the daemon falls back to the default profile (prior behavior).
// Named to match RunContext/PersistedRun and avoid confusion with
// AWSConfig.Profile (an AWS shared-config profile).
CredProfile string `json:"cred_profile,omitempty"`
PolicyYAML map[string][]byte `json:"policy_yaml,omitempty"`
PolicyRuleSets []PolicyRuleSetSpec `json:"policy_rule_sets,omitempty"`
HostGateway string `json:"host_gateway,omitempty"`
HostGatewayIP string `json:"host_gateway_ip,omitempty"`
AllowedHostPorts []int `json:"allowed_host_ports,omitempty"`
// SerialDevices are host serial devices this run may use. The daemon opens
// one RFC2217 listener per device and returns their addresses.
SerialDevices []SerialDeviceSpec `json:"serial_devices,omitempty"`
// SerialBindAddr is the address serial listeners bind to — the
// container-facing address of this run's network (loopback for host-net,
// the network gateway for bridge and Apple containers). RFC2217 has no
// authentication, so scoping the bind is the reachability control: a
// listener bound here is reachable from the container and from this
// machine, not from the network at large. Additive/optional: an older CLI
// omits it and the daemon falls back to the broker's configured BindAddr.
SerialBindAddr string `json:"serial_bind_addr,omitempty"`
// SerialPins names the exact port each device must come back on, keyed by
// device name. Sent on re-registration and restore: the container's
// MOAT_SERIAL_*_URL froze these ports at create, so the listeners must
// re-bind the same numbers — a fresh ephemeral port would be unreachable
// and the run's devices would silently die. A pinned port that is taken
// fails the registration rather than moving the device. Additive/optional.
SerialPins map[string]int `json:"serial_pins,omitempty"`
}
RegisterRequest is sent to POST /v1/runs.
func (*RegisterRequest) ToRunContext ¶
func (req *RegisterRequest) ToRunContext() *RunContext
ToRunContext converts a RegisterRequest into a RunContext.
type RegisterResponse ¶
type RegisterResponse struct {
AuthToken string `json:"auth_token"`
ProxyPort int `json:"proxy_port"`
Error string `json:"error,omitempty"`
// SerialAddrs maps each approved device name to the host:port of its
// RFC2217 listener. Empty when the run requested no devices.
SerialAddrs map[string]string `json:"serial_addrs,omitempty"`
}
RegisterResponse is returned from POST /v1/runs.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a thread-safe mapping of auth tokens to RunContexts. It provides the central lookup mechanism for the daemon proxy to resolve incoming requests to their per-run configuration and credentials.
func (*Registry) List ¶
func (r *Registry) List() []*RunContext
List returns all registered RunContexts.
func (*Registry) Lookup ¶
func (r *Registry) Lookup(token string) (*RunContext, bool)
Lookup finds a RunContext by auth token.
func (*Registry) Register ¶
func (r *Registry) Register(rc *RunContext) string
Register adds a RunContext and returns a generated auth token. The token is a 32-byte cryptographically random hex string.
func (*Registry) RegisterWithToken ¶
func (r *Registry) RegisterWithToken(rc *RunContext, token string)
RegisterWithToken adds a RunContext with a specific auth token. This is used for re-registration after a daemon restart so the container can keep using the same proxy auth token it was configured with.
func (*Registry) Unregister ¶
Unregister removes a RunContext by its auth token.
func (*Registry) UnregisterAndGet ¶ added in v0.5.0
func (r *Registry) UnregisterAndGet(token string) (*RunContext, bool)
UnregisterAndGet atomically removes and returns a RunContext by its auth token. Returns nil, false if the token is not found.
func (*Registry) UnregisterAndGetWithCount ¶ added in v0.5.0
func (r *Registry) UnregisterAndGetWithCount(token string) (rc *RunContext, found bool, remaining int)
UnregisterAndGetWithCount atomically removes a RunContext and returns both the removed context and the remaining count. This avoids a TOCTOU race where a new registration could slip in between UnregisterAndGet and a separate Count() call.
func (*Registry) UpdateContainerID ¶
UpdateContainerID sets the container ID for a registered run. Returns false if the token is not found.
type RemoveHeaderSpec ¶
RemoveHeaderSpec describes a header to remove from requests.
type RouteRegistration ¶
RouteRegistration is sent to POST /v1/routes/{agent}.
type RunContext ¶
type RunContext struct {
RunID string `json:"run_id"`
ContainerID string `json:"container_id,omitempty"`
AuthToken string `json:"auth_token"`
Credentials map[string][]CredentialEntry `json:"credentials"`
CredentialBundles map[string][]credential.Bundle `json:"-"` // contains secrets; never serialize
ExtraHeaders map[string][]ExtraHeaderEntry `json:"extra_headers"`
RemoveHeaders map[string][]string `json:"remove_headers"`
TokenSubstitutions map[string]TokenSubstitutionEntry `json:"token_substitutions"`
ResponseTransformers map[string][]credential.ResponseTransformer `json:"-"` // not serialized
MCPServers []config.MCPServerConfig `json:"mcp_servers,omitempty"`
NetworkPolicy string `json:"network_policy,omitempty"`
NetworkAllow []string `json:"network_allow,omitempty"`
NetworkRules []netrules.HostRules `json:"network_rules,omitempty"`
AWSConfig *AWSConfig `json:"aws_config,omitempty"`
TransformerSpecs []TransformerSpec `json:"transformer_specs,omitempty"`
Grants []string `json:"grants,omitempty"`
CopilotGitHubAuth bool `json:"copilot_github_auth,omitempty"`
HostGateway string `json:"host_gateway,omitempty"`
HostGatewayIP string `json:"host_gateway_ip,omitempty"` // actual IP for forwarding allowed host traffic
AllowedHostPorts []int `json:"allowed_host_ports,omitempty"`
// Serial state, for persistence across daemon restarts. SerialDevices and
// SerialBindAddr are the registration request's; SerialAddrs is what the
// container's frozen MOAT_SERIAL_*_URL points at, so a restore must
// re-open each listener on the same port.
SerialDevices []SerialDeviceSpec `json:"serial_devices,omitempty"`
SerialBindAddr string `json:"serial_bind_addr,omitempty"`
SerialAddrs map[string]string `json:"serial_addrs,omitempty"`
// CredProfile is the credential profile this run was created under (from
// the CLI's --profile/MOAT_PROFILE). The daemon is shared across profiles,
// so token refresh must scope to this value rather than the daemon
// process's own credential.ActiveProfile. Empty means the default profile.
CredProfile string `json:"cred_profile,omitempty"`
RegisteredAt time.Time `json:"registered_at"`
// Persist the exact registered policy, never reload it from the workspace.
PolicyYAML map[string][]byte `json:"policy_yaml,omitempty"`
PolicyRuleSets []PolicyRuleSetSpec `json:"policy_rule_sets,omitempty"`
KeepEngines map[string]*keeplib.Engine `json:"-"` // compiled Keep policy engines per scope
// contains filtered or unexported fields
}
RunContext holds per-run proxy state. It implements credential.ProxyConfigurer so providers can configure it identically to how they configure proxy.Proxy.
func NewRunContext ¶
func NewRunContext(runID string) *RunContext
NewRunContext creates a new RunContext for a run.
func (*RunContext) AddExtraHeader ¶
func (rc *RunContext) AddExtraHeader(host, headerName, headerValue string)
AddExtraHeader implements credential.ProxyConfigurer.
func (*RunContext) AddResponseTransformer ¶
func (rc *RunContext) AddResponseTransformer(host string, transformer credential.ResponseTransformer)
AddResponseTransformer implements credential.ProxyConfigurer.
func (*RunContext) CancelRefresh ¶
func (rc *RunContext) CancelRefresh()
CancelRefresh cancels the token refresh goroutine, if any. Safe to call concurrently and multiple times.
func (*RunContext) Close ¶ added in v0.5.0
func (rc *RunContext) Close()
Close releases resources held by this RunContext, including all Keep engines. Safe to call concurrently and multiple times.
func (*RunContext) CredentialBundleGrants ¶ added in v0.8.0
func (rc *RunContext) CredentialBundleGrants() []string
CredentialBundleGrants returns the distinct grants that have a bundle installed, in a stable order.
Bundles carry real secrets and are deliberately not serialized, so a run holding one cannot hand its values to the daemon in a registration request. It sends these grant names instead and the daemon resolves the current credential from the encrypted store itself — see RegisterRequest.CredentialRefs.
func (*RunContext) GetContainerID ¶
func (rc *RunContext) GetContainerID() string
GetContainerID returns the container ID safely.
func (*RunContext) GetCredential ¶
func (rc *RunContext) GetCredential(host string) (CredentialEntry, bool)
GetCredential returns the first credential for a host, checking host:port fallback. Use GetCredentials to retrieve all credentials when multiple grants target the same host.
func (*RunContext) GetCredentials ¶ added in v0.5.0
func (rc *RunContext) GetCredentials(host string) []CredentialEntry
GetCredentials returns all credentials for a host, checking host:port fallback.
func (*RunContext) GetExtraHeaders ¶
func (rc *RunContext) GetExtraHeaders(host string) []ExtraHeaderEntry
GetExtraHeaders returns extra headers for a host, checking host:port fallback.
func (*RunContext) GetRemoveHeaders ¶
func (rc *RunContext) GetRemoveHeaders(host string) []string
GetRemoveHeaders returns headers to remove for a host, checking host:port fallback.
func (*RunContext) GetResponseTransformers ¶
func (rc *RunContext) GetResponseTransformers(host string) []credential.ResponseTransformer
GetResponseTransformers returns response transformers for a host, checking host:port fallback.
func (*RunContext) GetTokenSubstitution ¶
func (rc *RunContext) GetTokenSubstitution(host string) (TokenSubstitutionEntry, bool)
GetTokenSubstitution returns the token substitution for a host, checking host:port fallback.
func (*RunContext) RemoveRequestHeader ¶
func (rc *RunContext) RemoveRequestHeader(host, headerName string)
RemoveRequestHeader implements credential.ProxyConfigurer.
func (*RunContext) SetAWSHandler ¶
func (rc *RunContext) SetAWSHandler(h http.Handler)
SetAWSHandler stores the AWS credential endpoint handler for this run.
func (*RunContext) SetContainerID ¶ added in v0.5.0
func (rc *RunContext) SetContainerID(id string)
SetContainerID updates the container ID safely.
func (*RunContext) SetCredential ¶
func (rc *RunContext) SetCredential(host, value string)
SetCredential implements credential.ProxyConfigurer.
func (*RunContext) SetCredentialBundle ¶ added in v0.8.0
func (rc *RunContext) SetCredentialBundle(host string, bundle credential.Bundle)
SetCredentialBundle implements credential.BundleConfigurer.
func (*RunContext) SetCredentialHeader ¶
func (rc *RunContext) SetCredentialHeader(host, headerName, headerValue string)
SetCredentialHeader implements credential.ProxyConfigurer.
func (*RunContext) SetCredentialWithGrant ¶
func (rc *RunContext) SetCredentialWithGrant(host, headerName, headerValue, grant string)
SetCredentialWithGrant implements credential.ProxyConfigurer. When multiple grants target the same host (e.g., "claude" and "anthropic" on api.anthropic.com), each is stored separately. If an entry with the same grant and header name already exists for the host (e.g., during token refresh), it is updated in place rather than duplicated.
func (*RunContext) SetRefreshCancel ¶
func (rc *RunContext) SetRefreshCancel(cancel context.CancelFunc)
SetRefreshCancel stores the cancel function for the token refresh goroutine.
func (*RunContext) SetTokenSubstitution ¶
func (rc *RunContext) SetTokenSubstitution(host, placeholder, realToken string)
SetTokenSubstitution implements credential.ProxyConfigurer.
func (*RunContext) ToProxyContextData ¶
func (rc *RunContext) ToProxyContextData() *proxy.RunContextData
ToProxyContextData converts this RunContext into a proxy.RunContextData for use in per-request credential resolution.
type RunInfo ¶
type RunInfo struct {
RunID string `json:"run_id"`
ContainerID string `json:"container_id,omitempty"`
RegisteredAt string `json:"registered_at"`
}
RunInfo is an element of the list returned by GET /v1/runs.
type RunPersister ¶ added in v0.3.2
type RunPersister struct {
// contains filtered or unexported fields
}
RunPersister manages saving and loading the run registry to disk.
func NewRunPersister ¶ added in v0.3.2
func NewRunPersister(path string, registry *Registry) *RunPersister
NewRunPersister creates a persister that saves to the given file path.
func (*RunPersister) Flush ¶ added in v0.3.2
func (p *RunPersister) Flush() error
Flush stops any pending debounce timer and performs a synchronous Save. Call during daemon shutdown to ensure the final state is persisted.
func (*RunPersister) Load ¶ added in v0.3.2
func (p *RunPersister) Load() ([]PersistedRun, error)
Load reads persisted runs from disk. Returns nil, nil if the file doesn't exist. Handles both the versioned format ({"version":1,"runs":[...]}) and the legacy bare-array format for backwards compatibility.
func (*RunPersister) Save ¶ added in v0.3.2
func (p *RunPersister) Save() error
Save serializes the current registry to disk atomically (temp file + rename). The file is written with 0600 permissions. Safe to call concurrently.
func (*RunPersister) SaveDebounced ¶ added in v0.3.2
func (p *RunPersister) SaveDebounced()
SaveDebounced coalesces rapid save calls with a short timer. Safe to call from multiple goroutines.
type SerialDeviceSpec ¶ added in v0.8.0
type SerialDeviceSpec struct {
Name string `json:"name"` // config name, e.g. "esp32"
Path string `json:"path"` // host device node
VID string `json:"vid,omitempty"` // for audit entries
PID string `json:"pid,omitempty"` // for audit entries
Serial string `json:"serial,omitempty"` // for audit entries
PortPath string `json:"port_path,omitempty"` // for audit entries
Interface string `json:"interface,omitempty"` // for audit entries
Record string `json:"record,omitempty"` // "events" (default) or "full"
// Baud is the initial line rate from moat.yaml's `baud:`. Zero means the
// port opens at whatever rate the driver default is and the client's
// SET-BAUDRATE governs. Applied at session open; an RFC2217 client that
// sends its own SET-BAUDRATE overrides it.
Baud int `json:"baud,omitempty"`
}
SerialDeviceSpec is one approved serial device, already resolved and pinned by the CLI. The daemon does not re-resolve it: enumeration and pin checking happen in pre-flight so a mismatch fails before the container is created.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the daemon's HTTP API server over a Unix socket.
func NewServer ¶
NewServer creates a daemon API server that will listen on the given Unix socket path.
func (*Server) ListenSerialPinned ¶ added in v0.8.0
func (s *Server) ListenSerialPinned(rc *RunContext, specs []SerialDeviceSpec, bindAddr string, pinned map[string]string) (map[string]string, error)
ListenSerialPinned re-opens a restored run's serial listeners on the exact ports the container's frozen MOAT_SERIAL_*_URLs point at. A daemon restart must not move a device, because the container cannot learn the new address. A port that cannot be re-bound fails with an error naming the device, rather than silently leaving the device dead or rebinding it elsewhere.
func (*Server) SetOnEmpty ¶
func (s *Server) SetOnEmpty(fn func())
SetOnEmpty sets a callback that is invoked when the last run is unregistered.
func (*Server) SetOnRegister ¶
func (s *Server) SetOnRegister(fn func())
SetOnRegister sets a callback invoked when a new run is registered.
func (*Server) SetOnShutdown ¶
func (s *Server) SetOnShutdown(fn func())
SetOnShutdown sets a callback that is invoked when shutdown is requested via the API. This should signal the main daemon loop to exit (e.g., by sending SIGTERM to self).
func (*Server) SetOnUnregister ¶
SetOnUnregister sets a callback that is invoked when a run is unregistered. The callback receives the run ID for per-run resource cleanup.
func (*Server) SetPersister ¶ added in v0.3.2
func (s *Server) SetPersister(p *RunPersister)
SetPersister sets the run persister for saving registry state to disk.
func (*Server) SetProxyPort ¶
SetProxyPort updates the proxy port reported in API responses. Call after the credential proxy starts to set the actual port.
func (*Server) SetRoutes ¶
func (s *Server) SetRoutes(rt *routing.RouteTable)
SetRoutes sets the route table used for route registration handlers.
func (*Server) SetSerialBroker ¶ added in v0.8.0
func (s *Server) SetSerialBroker(b *serialbroker.Broker)
SetSerialBroker attaches the serial device broker. Without one, a run that requests devices is rejected rather than silently starting with no access.
type TokenSubstitutionEntry ¶
type TokenSubstitutionEntry struct {
Placeholder string `json:"placeholder"`
RealToken string `json:"real_token"`
}
TokenSubstitutionEntry holds a placeholder-to-real-token mapping.
type TokenSubstitutionSpec ¶
type TokenSubstitutionSpec struct {
Host string `json:"host"`
Placeholder string `json:"placeholder"`
RealToken string `json:"real_token"`
}
TokenSubstitutionSpec describes a token substitution.
type TransformerSpec ¶
type TransformerSpec struct {
Host string `json:"host"`
Kind string `json:"kind"` // "oauth-endpoint-workaround" or "response-scrub"
}
TransformerSpec describes a response transformer to apply for a host. Since transformers are Go functions (not serializable), this spec allows the daemon to reconstruct them from well-known kinds.
type UpdateRunRequest ¶
type UpdateRunRequest struct {
ContainerID string `json:"container_id"`
}
UpdateRunRequest is sent to PATCH /v1/runs/{token}.