Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
d70acad
feat(mailbox): stage durable local admission and bounded guest submis…
auser Nov 10, 2026
9ca5bf6
Merge main mailbox lifecycle updates into local durable branch
auser Nov 10, 2026
8448257
feat(mailbox): define typed reports and reader delivery contract
auser Nov 10, 2026
2cd2e34
feat(mailbox): verify guest reader enrollment and signed delivery con…
auser Nov 10, 2026
dfc1691
fix(mailbox): reconcile runtime refusal and process teardown
auser Nov 10, 2026
454738f
feat(mailbox): journal signed guest leases and bind cold boot ACK keys
auser Nov 11, 2026
d7848da
feat(mailbox): carry boot-bound ACK key into guest activation
auser Nov 11, 2026
4bc9e6e
wip(mailbox): hand off boot-bound reader foundation
auser Nov 11, 2026
634d1cc
wip(mailbox): wire boot-bound guest Receive and explicit ACK
auser Nov 11, 2026
3d3d860
fix(mailbox): gate durable verdict behind independent authority
auser Nov 11, 2026
2dbda40
feat(mailbox): gate journal acceptance on independent anchor
auser Nov 11, 2026
d611677
Merge mailbox dev-ephemeral posture gate
auser Nov 11, 2026
4b60a0b
test(mailbox): refuse unanchored durable verdicts
auser Nov 11, 2026
784e7ad
test(mailbox): name missing posture refusal accurately
auser Nov 11, 2026
6e75656
test(mailbox): retire obsolete durable helper fixture
auser Nov 11, 2026
5d50caa
test(mailbox): retire obsolete durable helper fixture
auser Nov 11, 2026
771a822
fix(mailbox): verify recovered reader controls and ACK retries
auser Nov 11, 2026
cac0122
fix(mailbox): require signed ACK after guest lease
auser Nov 11, 2026
2f01b62
feat(mailbox): bind report Cast policy to signed plan
auser Nov 11, 2026
9112d8a
test(mailbox): retain legacy grant fixtures without report policy
auser Nov 11, 2026
a773195
feat(mailbox): distinguish local durable receipt from anchored accept…
auser Nov 11, 2026
62451ae
docs(mailbox): preserve local acceptance proof on exact retry
auser Nov 11, 2026
d7ec5c8
feat(mailbox): bind journal history to versioned custody
auser Nov 11, 2026
c56bf7b
test(mailbox): refuse forged serialized history references
auser Nov 11, 2026
382e3e4
Merge commit '62451aeb8db53ee040d5fdd9a6327ffacf89cbb2' into feat/463…
auser Nov 11, 2026
037154b
fix(mailbox): reconcile report grant in recovered lease
auser Nov 11, 2026
fc607ba
feat(mailbox): integrate verified envelope and Reply authority
auser Nov 11, 2026
e436a67
feat(mailbox): integrate borrowed verified admission capsule
auser Nov 11, 2026
629f98b
feat(mailbox): sign local receipts in V2 domain
auser Nov 11, 2026
b1d351c
feat(mailbox): stage local journal acceptance and exact retries
auser Nov 11, 2026
434cca0
fix(mailbox): retain optional report field in guest reader grant
auser Nov 11, 2026
54a47bb
feat(mailbox): stage signed restart custody with separate purpose keys
auser Nov 11, 2026
baa6dd1
feat(mailbox): commit signed terminal expiry before disclosure
auser Nov 11, 2026
23e9258
feat(mailbox): bind local receipts to existing journal service
auser Nov 11, 2026
57bbdab
feat(mailbox): require signed local receipt for guest outbox
auser Nov 12, 2026
385619f
feat(mailbox): bind retained purpose keys to launch template
auser Nov 12, 2026
c656ac7
feat(mailbox): bind signed local retention to custody
auser Nov 12, 2026
af9ac5c
fix(mailbox): keep local journal opener test-scoped until handoff
auser Nov 12, 2026
bf8972b
feat(mailbox): enforce local retention window at admission
auser Nov 12, 2026
e189e1f
docs(mailbox): require configured local retention ceiling
auser Nov 12, 2026
9612cb2
feat(mailbox): configure signed retention and host ceiling
auser Nov 12, 2026
af9b4d3
test(mailbox): initialize retention ceiling fixture directly
auser Nov 12, 2026
a389daa
feat(mailbox): checkpoint local journal with retained tombstones
auser Nov 12, 2026
367eccd
feat(mailbox): serve retained local durable production mailbox
auser Nov 12, 2026
406a97b
fix(cli): keep mailbox retention help within summary limit
auser Nov 12, 2026
33030f5
Merge origin/main into feat/4630-durable-mailbox-journal
auser Nov 12, 2026
c82706b
Merge origin/main into feat/4630-durable-mailbox-journal
auser Nov 12, 2026
f5d906b
fix(mailbox): use live admission clock and distinct spool identities
auser Nov 12, 2026
543efc7
Merge remote-tracking branch 'origin/main' into feat/4630-durable-mai…
auser Nov 12, 2026
de0f06a
fix(lab): run KVM witness without root and check legacy refusal
auser Nov 12, 2026
c8b755f
fix(mailbox): keep Unix journal portable and enable KVM user session
auser Nov 12, 2026
72715fb
fix(lab): pin Firecracker witness and account for uncertain creation
auser Nov 13, 2026
34e6f9f
Merge remote-tracking branch 'origin/main' into feat/4630-durable-mai…
auser Nov 13, 2026
2999b08
fix(lab): pin Firecracker for isolated GCP homes
auser Nov 13, 2026
538a642
Merge remote-tracking branch 'origin/main' into feat/4630-durable-mai…
auser Nov 13, 2026
94caaf6
docs(xtask): record merged mailbox footprint measurement
auser Nov 13, 2026
f7536f6
Merge current main into local-durable mailbox branch
auser Nov 13, 2026
e98e1b7
Merge remote-tracking branch 'origin/main' into feat/4630-durable-mai…
auser Nov 13, 2026
d204ce5
Merge remote-tracking branch 'origin/main' into feat/4630-durable-mai…
auser Nov 13, 2026
e90577f
fix(agentd): place safety rationale on Linux test block
auser Nov 13, 2026
2e5ec98
test(agentd): make mailbox fixture and ACK failure deterministic
auser Nov 13, 2026
076468d
Merge remote-tracking branch 'origin/main' into feat/4630-durable-mai…
auser Nov 13, 2026
7f06f10
chore: measure merged Rust footprint
auser Nov 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions crates/mvm-agentd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,47 @@ Specialized binaries keep privilege and dependency boundaries small:
`mvm-setpriv` crate, applies the final identity.

The guest treats host replies as protocol input, while the host treats every
guest request as untrusted. Secrets and authorization decisions remain
host-side; the agent receives only the capabilities and values needed for the
admitted execution.
guest request as untrusted. Host sealing and destination signing material
remain host-side; an opt-in mailbox reader additionally holds its own per-boot
ACK signing key inside the guest agent, not in the workload.

## Guest mailbox reader

Only a signed plan granting this workload's own mailbox can enable Receive.
The agent binds `/run/mvm/mailbox/receive.sock` when its separate boot ACK key
was provisioned. Dev's ephemeral host helper refuses reader frames; a production
mailbox with signed retention uses a locally anchored journal and boot-bound
Receive authority. The local protocol
is versioned, length-prefixed, and bounded; use `ApplicationReaderRequest` and
`ReaderResponse` from `mvm_core::mailbox::reader_wire`. Once backed by that
local destination, `Receive` returns a signed lease,
content type, and payload. Process the payload **before** sending
`Ack { lease }` on a new connection. An empty inbox and a refusal are not
successful delivery; reading never automatically ACKs.

For a guest application example, build:

```sh
cargo build -p mvm-agentd --example mailbox_reader_client --target aarch64-unknown-linux-musl
```

Run it without arguments to read without acknowledging; pass `--ack` only when
writing and flushing its stdout counts as completed processing. The returned
`Acknowledged { ack }` echoes the exact guest-signed operation after the local
journal and anchor commit. This is a trusted-local confirmation, **not** a
destination-signed, exportable ACK receipt. A lost delivery or ACK response is
**unknown**, not success. Retry with the same lease and signed ACK identity
while this boot's agent remains active, or reconcile against the durable
journal; a different ACK for the same lease conflicts.

`mailbox_writer_client <message>` stages into the Dev/legacy spool.
`mailbox_writer_client --local <message>` uses the separate immutable
`outbox-local` request for the production local-durable tier; the guest agent
retains it until it verifies the boot-bound signed V2 receipt. A response it
cannot verify is not acceptance. Both commands report **local staging only**,
not a destination receipt; verify the host-side receipt or journal before
claiming destination acceptance. Do not use the legacy spool for a production
local-durable claim.

## Bounded entrypoint capture

Expand Down
70 changes: 70 additions & 0 deletions crates/mvm-agentd/examples/mailbox_reader_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Guest application example: read one message from this workload's own inbox.
//! Pass `--ack` only after the application has processed and flushed its output.

use std::io::{self, Write as _};
use std::os::unix::net::UnixStream;
use std::time::Duration;

use mvm_core::mailbox::reader_wire::{
ApplicationReaderRequest, GUEST_READER_SOCKET_PATH, ReaderResponse, read_response,
write_application_request,
};

fn connect() -> io::Result<UnixStream> {
let stream = UnixStream::connect(GUEST_READER_SOCKET_PATH)?;
stream.set_read_timeout(Some(Duration::from_secs(12)))?;
stream.set_write_timeout(Some(Duration::from_secs(12)))?;
Ok(stream)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let ack = match std::env::args().skip(1).collect::<Vec<_>>().as_slice() {
[] => false,
[flag] if flag == "--ack" => true,
_ => return Err("usage: mailbox_reader_client [--ack]".into()),
};
let mut stream = connect()?;
write_application_request(&mut stream, &ApplicationReaderRequest::Receive)?;
let response = read_response(&mut stream)?;
let ReaderResponse::Delivery {
lease,
content_type,
payload,
} = response
else {
match response {
ReaderResponse::Empty => eprintln!("inbox empty"),
ReaderResponse::Refused { reason } => return Err(reason.into()),
_ => return Err("unexpected reader response".into()),
}
return Ok(());
};
// This example's application processing is writing and flushing stdout.
// A real application ACKs only after its own durable processing completes.
io::stdout().write_all(&payload)?;
io::stdout().flush()?;
eprintln!(
"received {} bytes as {} (message {:?}, delivery {:?})",
payload.len(),
content_type.name(),
lease.lease.message_id,
lease.lease.delivery_id
);
if ack {
let mut stream = connect()?;
write_application_request(
&mut stream,
&ApplicationReaderRequest::Ack {
lease: lease.clone(),
},
)?;
match read_response(&mut stream)? {
ReaderResponse::Acknowledged { ack: signed } if signed.ack.lease == *lease => {
eprintln!("locally acknowledged by MVM");
}
ReaderResponse::Refused { reason } => return Err(reason.into()),
_ => return Err("ACK response did not identify this lease".into()),
}
}
Ok(())
}
46 changes: 46 additions & 0 deletions crates/mvm-agentd/examples/mailbox_writer_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! Stage one guest-to-own-mailbox message. Local staging is not a signed
//! destination acceptance receipt; inspect the host journal for durability.

use std::path::Path;

use mvm_agentd::mailbox_outbox::{LOCAL_OUTBOX_PATH, OUTBOX_PATH, write_outbox};
use mvm_contract::protocol::mailbox::PrincipalId;
use mvm_core::mailbox::frame::{OWN_MAILBOX_RECIPIENT, OutboxMessage};

fn destination(args: &[String]) -> Option<(&'static str, &str)> {
match args {
[body] if !body.is_empty() && body != "--local" => Some((OUTBOX_PATH, body)),
[flag, body] if flag == "--local" && !body.is_empty() => Some((LOCAL_OUTBOX_PATH, body)),
_ => None,
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().skip(1).collect();
let (outbox, body) =
destination(&args).ok_or("usage: mailbox_writer_client [--local] <message>")?;
let message = OutboxMessage::new(
PrincipalId::from_bytes(OWN_MAILBOX_RECIPIENT),
body.as_bytes().to_vec(),
);
write_outbox(Path::new(outbox), &message)?;
eprintln!("staged locally: message {:?}", message.message_id);
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn the_local_option_chooses_the_receipt_bearing_outbox_only_when_explicit() {
assert_eq!(destination(&["hello".into()]), Some((OUTBOX_PATH, "hello")));
assert_eq!(
destination(&["--local".into(), "hello".into()]),
Some((LOCAL_OUTBOX_PATH, "hello"))
);
assert_eq!(destination(&["--local".into()]), None);
assert_eq!(destination(&["".into()]), None);
assert_eq!(destination(&["one".into(), "two".into()]), None);
}
}
34 changes: 24 additions & 10 deletions crates/mvm-agentd/src/bin/mvm-guest-agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ mod init;
mod interactive;
#[path = "mvm-guest-agent/mailbox.rs"]
mod mailbox;
#[path = "mvm-guest-agent/mailbox_reader.rs"]
mod mailbox_reader;
#[path = "mvm-guest-agent/monitoring.rs"]
mod monitoring;
#[path = "mvm-guest-agent/port_forward.rs"]
Expand Down Expand Up @@ -221,7 +223,7 @@ impl ColdContinuation {
}

fn complete_cold_response(
file: &mut std::fs::File,
file: &mut impl Write,
session: &mut AuthenticatedSession,
next: Option<ColdContinuation>,
response: &GuestResponse,
Expand Down Expand Up @@ -1656,6 +1658,17 @@ mod tests {

#[test]
fn cold_ack_write_failure_cannot_enable_continuation() {
struct RejectWrite;
impl Write for RejectWrite {
fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
Err(std::io::ErrorKind::BrokenPipe.into())
}

fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

let (mut host, mut guest) = UnixStream::pair().unwrap();
host.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
guest
Expand All @@ -1672,18 +1685,19 @@ mod tests {
let (mut session, _) =
AuthenticatedSession::host_retaining_peer(&mut host, "ack-failure", host_key).unwrap();
peer.join().unwrap();
let descriptor: std::os::fd::OwnedFd = host.into();
let mut file = std::fs::File::from(descriptor);
// This tests the real protected write seam, not activation or native
// ownership. A candidate ACK alone cannot create a continuation.
let Err(error) = complete_cold_response(
&mut RejectWrite,
&mut session,
Some(ColdContinuation::Exec),
&GuestResponse::ActivateEnvironmentAck,
) else {
panic!("the injected writer must fail before continuation");
};
assert!(
complete_cold_response(
&mut file,
&mut session,
Some(ColdContinuation::Exec),
&GuestResponse::ActivateEnvironmentAck,
)
.is_err()
format!("{error:#}").contains("control frame write failed"),
"the injected writer must fail before continuation"
);
}

Expand Down
2 changes: 2 additions & 0 deletions crates/mvm-agentd/src/bin/mvm-guest-agent/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ pub(crate) fn apply_activation(
// The telemetry signing key is the egress client's, mode 0400; read it now
// or the listener can never answer a session.
crate::telemetry::capture_session_keys_before_privilege_drop();
crate::mailbox::capture_ack_key_before_privilege_drop();
guest_mount::drop_guest_agent_privilege(guest_mount::WORKLOAD_UID, guest_mount::WORKLOAD_GID)?;
crate::mailbox::protect_ack_key_after_privilege_drop()?;

boot_state.set_activation(ActivationState::Activated);
Ok(())
Expand Down
Loading
Loading