AegisAgent Control Command Protocol¶
Status: target protocol design
Date: 2026-06-28
1. Purpose¶
The Control Command Protocol lets aegis-gateway safely instruct aegis-node-sensor to control local workloads: start, pause, resume, kill, snapshot, quarantine, ban, update policy, or change local enforcement configuration.
This protocol is security-critical. A forged or replayed control command can kill workloads, release quarantines, or disable controls. Therefore:
- every command is signed
- every command is tenant-bound
- every command has an expiry
- every command has a nonce
- replay is rejected
- execution is idempotent
- every command result is ACK/NACKed
- control actions emit runtime events and receipts
2. Security goals¶
- Authenticity: sensor verifies command came from trusted gateway key.
- Tenant binding: command cannot control a different tenant's workload.
- Target binding: command cannot be retargeted to another run/agent/sandbox.
- Freshness: expired commands are rejected.
- Replay protection: reused nonce/command ID is rejected or idempotently ACKed.
- Integrity: command payload cannot be modified after signing.
- Least privilege: sensor executes only typed command handlers, never arbitrary shell from payload.
- Evidence: accepted and executed commands are recorded as runtime events and receipts.
- Fail closed: invalid, unsigned, expired, or replayed commands are rejected.
3. Entities and keys¶
3.1 Gateway command signing key¶
Recommended signing algorithm: Ed25519.
- Gateway holds private command signing key in KMS/HSM or local dev file.
- Sensor stores pinned gateway public key set from registration/config.
- Key IDs support rotation.
3.2 Sensor identity key¶
Optional but recommended:
- Sensor has its own key pair.
- Sensor signs ACK/NACK result body.
- Gateway verifies result signatures for stronger non-repudiation.
3.3 Key rotation¶
- Gateway publishes key set with
kid,not_before,not_after, and status. - Sensor accepts active and grace-period keys.
update_sensor_configrotates pinned keys only when signed by a currently trusted key.- Emergency rotation requires out-of-band sensor re-registration or operator action.
4. Canonicalization¶
Commands are signed over deterministic canonical bytes.
Canonical version: aegis-command-jcs-1.
Rules:
- JSON object keys sorted by Unicode code point.
- Compact separators.
- Raw UTF-8.
- No non-finite numbers.
signaturefield excluded from signed body.signature_algorithmandsigner_key_idincluded in signed body.- All timestamps are RFC3339 UTC strings.
Signed body is:
Signature is:
5. Command schema¶
{
"schema_version": 1,
"canonical_version": "aegis-command-jcs-1",
"command_id": "cmd_01HY...",
"tenant_id": "tenant_123",
"target_type": "run",
"target_id": "run_01HY...",
"action": "kill_run",
"reason": "blocked egress exfil attempt",
"issued_by": "user:security-admin@example.com",
"issued_at": "2026-06-28T12:00:00Z",
"expires_at": "2026-06-28T12:05:00Z",
"nonce": "base64url-128-bit-random",
"requires_ack": true,
"receipt_required": true,
"payload": {
"kill_signal": "SIGKILL",
"preserve_workspace": true
},
"signature_algorithm": "ed25519",
"signer_key_id": "gw-key-2026-06",
"signature": "base64url-signature"
}
6. Target types¶
target_type |
target_id examples |
Notes |
|---|---|---|
sensor |
sensor_node_id |
sensor config/policy update |
run |
run_id |
pause/resume/kill/snapshot |
sandbox |
sandbox_id |
runtime-level actions |
agent |
agent_id |
freeze/unfreeze/ban |
workspace |
workspace_id or run_id |
quarantine/release/delete |
mcp_server |
server_key or ID |
quarantine/restore |
mcp_tool |
server/tool composite | disable/enable |
tool |
tool ID/name | disable/enable |
destination |
domain/IP/CIDR | block/unblock egress |
credential |
credential scope/ref | revoke/rotate |
policy_bundle |
bundle ID/version | update local policy |
7. Command actions¶
Required command actions:
start_runpause_runresume_runkill_runsnapshot_workspacequarantine_workspacerelease_workspaceban_agentunban_agentban_fingerprintunban_fingerprintfreeze_agentunfreeze_agentrevoke_tokenrotate_tokenblock_destinationunblock_destinationdisable_toolenable_toolquarantine_mcp_serverrestore_mcp_servercollect_evidenceupdate_policyupdate_sensor_config
8. Command validation algorithm on sensor¶
For every received command:
- Parse JSON; reject malformed.
- Validate required fields and
schema_version. - Check
tenant_idequals sensor tenant binding. - Check
target_typeandtarget_idare in sensor's local scope. - Check
issued_atis not too far in the future. - Check
expires_atis after current time and within allowed max TTL. - Check nonce not seen for
(tenant_id, signer_key_id). - Canonicalize command without
signature. - Resolve
signer_key_idto pinned gateway public key. - Verify Ed25519 signature.
- Check command authorization scope if local config includes allowed command scopes.
- Check idempotency by
command_id. - Execute typed handler or return idempotent previous result.
- Persist ACK/NACK and emit event.
Invalid commands are never executed.
9. Replay protection¶
Sensor maintains a durable replay cache:
Columns:
tenant_idcommand_idnoncesigner_key_idexpires_atstatusresult_hashfirst_seen_atlast_seen_at
Rules:
- Nonce is unique until expiry plus replay grace window.
command_idis unique permanently or for retention window.- If same valid command is delivered twice, return previous ACK result.
- If same
command_idappears with different canonical body, reject as tamper. - Expired replay cache entries are compacted after retention.
10. Idempotency model¶
Commands are designed to be retry-safe.
| Action | Idempotent behavior |
|---|---|
pause_run |
paused run remains paused; ACK success |
resume_run |
running run remains running; ACK success if not killed/quarantined |
kill_run |
killed/missing terminal run returns prior terminal status |
quarantine_workspace |
already quarantined workspace ACKs success with existing quarantine ID |
ban_agent |
existing active ban ACKs success with existing ban ID |
block_destination |
existing block ACKs success |
update_policy |
same bundle hash ACKs success; different hash with same command ID rejects |
start_run |
same run spec returns existing run; differing spec rejects |
11. Delivery mechanisms¶
11.1 Long polling¶
Sensor polls:
Response:
Simple and robust; good first implementation.
11.2 Server-sent events¶
Gateway streams command notifications. Sensor still fetches full command by ID to avoid losing commands on stream interruption.
11.3 WebSocket¶
Useful for low-latency enterprise control, but more operationally complex. Keep long-poll fallback.
12. ACK/NACK schema¶
Sensor posts:
Request:
{
"schema_version": 1,
"tenant_id": "tenant_123",
"command_id": "cmd_...",
"sensor_node_id": "sensor_...",
"status": "ack|nack|executed|failed|rejected|duplicate",
"received_at": "2026-06-28T12:00:01Z",
"executed_at": "2026-06-28T12:00:02Z",
"result_code": "ok|invalid_signature|expired|replay|target_not_found|execution_failed",
"message": "human readable summary without secrets",
"result": {
"run_id": "run_...",
"previous_state": "running",
"new_state": "killed"
},
"runtime_event_id": "evt_...",
"receipt_id": "rcpt_...",
"receipt_hash": "sha256hex",
"sensor_signature_algorithm": "ed25519",
"sensor_signer_key_id": "sensor-key-...",
"sensor_signature": "base64url-signature"
}
ACK/NACK is also idempotent. Gateway accepts duplicate ACKs if body hash matches prior result.
13. Gateway command state machine¶
stateDiagram-v2
[*] --> Created
Created --> Signed
Signed --> Delivered
Signed --> Expired: not delivered before expiry
Delivered --> Acked
Delivered --> Nacked
Delivered --> Executed
Delivered --> Failed
Acked --> Executed
Acked --> Failed
Executed --> Receipted
Failed --> Receipted: if receipt_required
Nacked --> Receipted: if receipt_required
Expired --> Receipted: if receipt_required
Command statuses:
createdsigneddeliveredackednackedexecutedfailedexpiredreceipt_failed
14. Storage model¶
14.1 control_commands¶
Required fields:
idtenant_idcommand_idtarget_typetarget_idactionreasonissued_byissued_atexpires_atnoncepayload_jsonrequires_ackreceipt_requiredcanonical_versionsignature_algorithmsigner_key_idsignaturestatuscreated_at
Indexes:
- unique
(tenant_id, command_id) (tenant_id, target_type, target_id, created_at)(tenant_id, status, expires_at)
14.2 control_action_results¶
Required fields:
idtenant_idcommand_idsensor_node_idstatusreceived_atexecuted_atresult_codemessageresult_jsonruntime_event_idreceipt_idreceipt_hashsensor_signaturecreated_at
Indexes:
- unique
(tenant_id, command_id, sensor_node_id) (tenant_id, sensor_node_id, created_at)
15. Receipt requirements¶
A receipt is required for:
kill_runpause_runfor policy/security reasonsquarantine_workspacerelease_workspaceban_agentunban_agentban_fingerprintunban_fingerprintfreeze_agentunfreeze_agentrevoke_tokenblock_destinationquarantine_mcp_serverrestore_mcp_servercollect_evidenceupdate_policyupdate_sensor_config
Receipt fields should include:
- command ID
- tenant ID
- target type/ID
- action
- actor (
issued_by) - reason
- sensor node ID
- decision/control result
- previous state and new state hashes where applicable
prev_receipt_hashreceipt_hash- signature
- timestamp
Protected control actions should not be considered successful until the gateway stores the command result receipt. If the sensor executed but gateway receipt storage fails, the gateway marks the command receipt_failed and emits a critical SOC event.
16. Sensor command execution handlers¶
16.1 Handler interface¶
trait CommandHandler {
fn action(&self) -> CommandAction;
async fn validate(&self, ctx: &CommandContext, payload: &Value) -> Result<(), CommandError>;
async fn execute(&self, ctx: &CommandContext, payload: &Value) -> Result<CommandResult, CommandError>;
}
16.2 Handler rules¶
- No command may run arbitrary shell based on payload.
- All paths are resolved inside known Aegis directories/workspaces.
- All destructive operations require
reason. - All handlers are idempotent.
- All handlers emit runtime event.
- All handlers return structured error code on failure.
17. Command-specific payloads¶
17.1 kill_run¶
{
"kill_signal": "SIGTERM|SIGKILL",
"grace_period_seconds": 10,
"preserve_workspace": true,
"collect_evidence": true
}
17.2 quarantine_workspace¶
{
"workspace_id": "workspace_or_run_id",
"preserve_files": true,
"make_read_only": true,
"attach_incident_id": "inc_..."
}
17.3 ban_fingerprint¶
{
"fingerprint_type": "image_digest|repo_hash|binary_hash|package_lock_hash|behavior_signature",
"fingerprint_value": "sha256:...",
"scope": "tenant|organization|global_admin",
"duration": "permanent|until_timestamp|until_manual_review|until_incident_closed",
"expires_at": null
}
17.4 update_sensor_config¶
{
"config_version": 42,
"mode": "enforce",
"policy_bundle_hash": "sha256hex",
"ban_version": 19,
"egress_rule_version": 7,
"effective_at": "2026-06-28T12:01:00Z"
}
18. Failure behavior¶
| Condition | Sensor behavior | Gateway behavior |
|---|---|---|
| invalid signature | reject, emit control_command_received with rejection |
mark rejected if ACK received; alert |
| unknown key ID | reject | alert/key distribution issue |
| expired command | reject | mark expired/rejected |
| replayed nonce | reject or duplicate ACK if same command | alert if canonical body differs |
| target not found | NACK | mark failed and receipt if required |
| handler failed | NACK with structured code | mark failed and create SOC event |
| ACK delivery failed | retry with backoff from sensor | command remains pending until expiry |
| gateway down after execution | sensor stores ACK result locally and retries | reconcile on reconnect |
| receipt write failed | command result marked receipt_failed |
protected control action requires investigation |
19. Authorization to issue commands¶
Gateway must authorize command creation before signing.
Inputs:
- actor identity and role
- tenant
- target ownership
- run/agent/sandbox state
- incident linkage
- policy bundle
- ban/quarantine state
- command type and risk
Rules:
- Tenant A cannot command Tenant B targets.
- Destructive actions require admin/security role or policy automation identity.
- Global bans require global admin.
- Release/delete quarantine requires explicit review permission.
- Policy automation can issue predefined commands but not arbitrary payloads.
20. API examples¶
20.1 Issue kill command¶
{
"target_type": "run",
"target_id": "run_123",
"action": "kill_run",
"reason": "unknown egress exfiltration attempt",
"payload": {
"kill_signal": "SIGKILL",
"preserve_workspace": true,
"collect_evidence": true
}
}
Response:
{
"command_id": "cmd_123",
"status": "signed",
"expires_at": "2026-06-28T12:05:00Z",
"receipt_required": true
}
20.2 ACK kill command¶
{
"tenant_id": "tenant_123",
"command_id": "cmd_123",
"sensor_node_id": "sensor_1",
"status": "executed",
"result_code": "ok",
"runtime_event_id": "evt_123",
"receipt_id": "rcpt_123",
"receipt_hash": "..."
}
21. Test plan¶
Unit tests¶
- canonical command body excludes only
signature - command signature verification
- invalid key ID rejected
- expired command rejected
- future-issued command rejected
- nonce replay rejected
- tenant mismatch rejected
- target scope mismatch rejected
- idempotent duplicate command returns previous result
Integration tests¶
- gateway signs
kill_run, sensor verifies and executes mock handler - sensor ACK updates command result
- gateway down after execution stores ACK locally and replays
- command result receipt emitted
- duplicate ACK accepted only if result hash matches
- same command ID with different body rejected
Security tests¶
- Tenant A cannot command Tenant B run
- unauthenticated command creation rejected
- unsigned command to sensor rejected
- tampered payload rejected
- replayed command rejected
- release quarantine requires correct role
22. Initial implementation scope¶
First PR should implement only:
- shared command schema crate
- Ed25519 sign/verify helper
- canonical command body tests
- gateway route to create signed command record
- sensor-side verifier library tests
- ACK/NACK route storing results
- one mock command action:
kill_run
Do not implement every command handler in the first PR. The protocol correctness is the foundation.