| Internet-Draft | Homing Pigeon v1 | October 2026 |
| Slutsky | Expires 3 May 2027 | [Page] |
This document specifies the Homing Pigeon message protocol, version 1: signed JSON job and result objects exchanged over a transport binding. On the email binding, a submitter sends one HMAC-SHA-384 signed JSON object to a mailbox; a runner polls that mailbox, verifies the signature, executes the job, and returns a signed result; the other bindings carry the same objects unchanged. The signature is the only authorization. The job object is protected end to end by per-job AEAD encryption, a required part of the protocol: implementations MUST support it, the reference software sends encrypted by default, and a receiver refuses a cleartext job unless the operator has explicitly enabled cleartext acceptance — in the reference software, only by a command-line parameter. The store-and-forward bindings open no inbound listener on the host that runs the job.¶
The DTLS binding changes the binding and not the messages: the same signed objects travel inside a direct DTLS 1.3 session between the submitter and the runner. It is a binding of version 1, not a new version of the protocol. The shared secret is the session's external pre-shared key; key exchange is ephemeral-only, so sessions have perfect forward secrecy; and once the handshake has authenticated the submitter, the runner opens the session with a greeting that discloses a tiered, responder-chosen summary of its platform. The binding opens an inbound listener on the runner's host and is off unless the owner enables it. It is an addition, never a replacement: the store-and-forward bindings remain the ones every submitter can reach.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 3 May 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
Some hosts cannot accept inbound connections. The protocol needs a store-and-forward queue so neither side opens a connection to the other. The first binding this document specifies is a mailbox: the submitter does not open a connection to the runner, and the runner does not open a connection to the submitter except to read and write that mailbox.¶
The exchange is two messages:¶
Submitter Mailbox Runner
| | |
| job message (signed JSON) | |
|--------------------------->| |
| | poll |
| |<---------------------------|
| | result message |
| | (signed JSON) |
| |<---------------------------|
| result (same mailbox, or | |
| signed reply_to) | |
|<---------------------------| |
¶
The deployment this protocol is written for is a person operating their own machines from a cloud-resident agent. The person holds the mailbox and the secret; the agent holds neither the host nor a network path to it. Email is the path that already exists between them. The runner is deliberately plain: it executes what the signature authorizes, on the owner's host, under the owner's account.¶
Version negotiation is not a handshake. The message carries v. This document defines v = 1 only. A receiver that does not implement version 1 MUST reject the message.¶
The protocol is specified in the open so that a submitter does not need the runner's source code. Any implementation of this document can submit jobs: the subject, the JSON object, and the MAC are all specified here, and the reference transport is email, which the submitter already speaks.¶
Two things are out of scope for this document and are established out of band: the mailbox address, and the shared secret. The sender asks the owner which address to use, and the owner provides the secret with it. Neither is discovered from the mail. Once the sender knows the address, the secret, and this specification, no further coordination is required.¶
This document specifies one version of the protocol: version 1. Its bindings are the email binding (Section 5), the file binding (Section 5.1), the NATS binding (Section 5.2), and the DTLS session binding (Section 19), which also carries the session greeting (Section 20). It consolidates this document's earlier text, which specified the email binding alone, with the DTLS binding text that was first drafted as a companion document. The companion draft that now carries the name draft-slutsky-homing-pigeon-v2-00 [I-D.slutsky-homing-pigeon-v2] is a different document: it specifies protocol version 2 (the determination, question, and answer exchange) and does not specify a binding.¶
A binding does not version the messages. The job and result objects are the same objects on every binding, byte for byte; their v member is 1, and a receiver MUST NOT reject an object for carrying v = 1 on any binding. A receiver that does not offer a binding is not non-conformant; it simply does not offer that binding.¶
The reference implementation speaks version 1, and it implements the bindings this document specifies, including the DTLS session binding of Section 19. A receiver following this document rejects any message v other than 1.¶
The sender also requires the shared secret to meet a minimum-entropy floor (Section 17.3). That floor is not a protocol version; it is a requirement of this protocol, and it binds version 1 senders and runners today (Section 17.3). Nothing else in the exchange changes shape.¶
Later work also layers behavior this document scopes out (Section 4) onto the base exchange: a local disclosure policy on results, queries against retained output, attestations, and task boards. Those behaviors ride in extra members (Section 8); they do not redefine any message specified here. One consequence a submitter needs: a receiver layered with a disclosure policy can withhold result output that a version 1 receiver would send, so the guidance in Section 13.2 binds only where no such policy is in force.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Job: a signed request that the runner execute work, or answer a liveness check.¶
Result: a signed report of what the runner did with one job.¶
Shared secret: an octet string known to the submitter and the runner. It is the HMAC key. It is not sent on the wire.¶
Default secret: the shared secret selected when key_id is absent.¶
Key identifier: the string in key_id. It is a label, not a secret.¶
Seen-set: the runner's record of job_id values already processed. It is local state. It is not a message.¶
Freshness window: how far issued_at may be from the runner's clock before the job is rejected.¶
Preamble: text in the message body before the signed JSON object. It is not signed.¶
LOCAL: a parameter or a check this document allows a receiver to set for itself. It is not negotiated.¶
Session: one DTLS association between a submitter (the initiator, in the DTLS client role) and a runner (the responder, in the DTLS server role). Defined in Section 19.¶
Greeting: the first application message of a session, sent by the runner after authentication completes (Section 20).¶
PSK identity: the pre_shared_key identity under which a secret is offered. The default secret's identity is homing-pigeon; an additional secret's is homing-pigeon: followed by its key_id.¶
Ticket: the opaque resumption identity a runner issues in a NewSessionTicket message.¶
This document specifies subjects, the signed object, the MAC, key_id, reply_to, replay, freshness, the job message, and the result message.¶
This document does not specify disclosure policy, queries, attestations, task boards, sensors, confinement, installers, or local models. A job MAY carry extra members for those uses. Receivers that do not implement them MUST ignore unknown members (Section 8) and MUST still reject an unknown v or an unknown job kind (Sections 8 and 12). Section 1 describes how those uses relate to this document.¶
This section specifies the email binding. The protocol itself needs only the store-and-forward queue from Section 1. Further bindings are specified in Sections 5.1 and 5.2; both carry the same messages, unchanged. A direct session binding, in which the same messages travel inside a DTLS session and the runner greets the submitter after authentication, is specified in Section 19.¶
The binding layer is abstract. A binding is an interchangeable carrier of the same messages: the objects, the MAC, the per-job encryption of Section 16, and the receiver checks of this document are identical on every binding. An implementation MAY substitute one binding for another, or add a binding, without changing the protocol; two parties interoperate on any binding both offer.¶
Conformance is per binding. This document specifies one version of the protocol, version 1, and an implementation conforms for each binding it offers; it need not offer every binding, and no binding is mandatory to implement: a conforming implementation offers at least one binding, and two parties interoperate on any binding both offer. Pairing is established out of band (Section 1) in either case. The email binding of this section is the binding every submitter environment already speaks, and the reference implementation enables it by default; that is guidance, not a requirement.¶
Two security properties are REQUIRED of the protocol on every binding, and they attach differently. The first is authentication of the object: the MAC of Section 7, verified as Section 14 specifies. That requirement attaches to the object, never to the transport. No authentication a binding performs, however strong, satisfies or relaxes it, because the MAC is the admission decision, it selects the key (Section 10), it binds the exact object, and it remains verifiable after any session has ended. The second is confidentiality of the object, in transit and at rest, between the submitter and the runner. That requirement attaches to the property, not to a mechanism. A binding whose carriage provides confidentiality end to end, between the holders of the shared secret, with no intermediary that can read the object or store it outside the parties' control, satisfies the property itself, and the per-job encryption of Section 16 is then OPTIONAL on that binding. Where a binding's carriage terminates at an intermediary, or stores the object outside the parties' control, the transport does not satisfy the property, and Section 16 is the mechanism that provides it; a message sent in cleartext by the explicit choice Section 16 permits is a message for which the confidentiality property is not claimed. Each binding specified in this document states which case it is: the email binding in this section, the file binding in Section 5.1, the NATS binding in Section 5.2, and the DTLS binding in Section 19.4.¶
The runner reads the mailbox with IMAP and writes with SMTP. Mail authentication is out of band. The signature check does not change with the mail credential.¶
IMAP and SMTP MUST be used over TLS. An implementation MUST NOT carry this protocol over a cleartext IMAP or SMTP session. The TLS version MUST be 1.2 or higher; TLS 1.0 and TLS 1.1 MUST NOT be used [RFC8996]. The recommendations of [RFC9325] apply. Cleartext mail access is obsolete [RFC8314]: implicit TLS is preferred, and where STARTTLS is used it MUST be required rather than opportunistic, and the peer certificate MUST be verified. TLS protects a hop. It does not hide the message from the mailbox provider (Section 17.2). The TLS requirements of this section therefore do not satisfy the confidentiality property stated above: the mailbox provider is an intermediary that stores the object and can read it. On this binding the confidentiality property is carried by the per-job encryption of Section 16, and the authentication property by the MAC, as it is on every binding.¶
An implementation MAY additionally support PGP/MIME protection of the mail on this binding. Such support is not required, and it is not this protocol's confidentiality mechanism: that mechanism is the per-job encryption of Section 16, which travels with the object on every binding.¶
The body is UTF-8 text in a text/plain part. This document does not register a media type. If the message is multipart, the receiver uses the first text/plain part that is not an attachment.¶
The message MUST be rejected before the MAC if the raw RFC822 object exceeds the receiver's size cap. The RECOMMENDED cap is 4194304 octets. A cap of 0 means no limit. This cap is LOCAL.¶
The file binding carries the same envelope as a file in a pair of watched directories on a filesystem both parties can reach. It exists for deployments where the submitter and the runner share a filesystem (a mounted volume, a synchronized folder, a removable medium) and for testing: it is the email binding's envelope with the mailbox removed, and nothing else changes.¶
The envelope mapping is byte-for-byte the email form: a subject line in the shape of Section 6, a blank line, then the signed body. A job is a regular file in the inbox directory. The file SHOULD be named <ident>.job.txt, where <ident> is the binding-local name of the job (the job_id serves), but a receiver MUST NOT condition acceptance on the name: every regular file in the inbox is read as a candidate job and judged by its contents, and symbolic links and directories are not read. A result is a file named <ident>.result.txt in the outbox directory. A receiver polls the directories on its ordinary poll cycle.¶
Acknowledgement is consumption. Whichever side has taken a file moves it into a sibling done/ directory; files are never deleted by the binding. A redelivery of the same job rewrites the same file name, and the seen-set (Sections 14 and 17.6) deduplicates the repeat: delivery is at-least-once, exactly as on the email binding (Section 17.10).¶
Two gates stand in series on this binding. The first is the inbox directory's operating-system access control: only principals the owner has given write access can present envelopes at all. The second is the signature, which remains the authorization, unchanged. A deployment MUST treat the directory's access control as a real gate and set it deliberately; the signature check does not excuse a world-writable inbox.¶
The binding's limits are the filesystem's. Both parties need access to the same directories, so the binding does not cross a network by itself (a network filesystem under it is the deployment's own affair, with its own failure modes). There is no store-and-forward beyond the disk, no ordering beyond arrival, and the size cap of Section 5 applies to envelope files.¶
Of the two security properties of Section 5, this binding provides neither itself, and needs neither from a transport: the carriage never leaves storage both parties already control, and no intermediary reads or holds the object. The MAC carries the authentication property, as on every binding. Whether Section 16 is applied on this binding is a local policy decision about the readers of that storage; a sender MAY apply it, and a receiver MUST be able to receive it, as Section 16 requires of every receiver.¶
The NATS binding carries the same envelope over core NATS publish/subscribe. It exists for deployments where submitter and runner already share a NATS broker: a low-latency pipe with no store-and-forward pretensions.¶
The envelope mapping is the email form carried as a message payload: the subject line of Section 6, a blank line, then the signed body, as one payload. Jobs are published on a jobs subject (a LOCAL name; the reference default is homing-pigeon.jobs). Results are published on a per-job subject formed from a LOCAL prefix and the job_id (the reference default is homing-pigeon.results.<job_id>).¶
Core NATS is at-most-once: the broker acknowledges nothing and retains nothing. A job published while no runner is subscribed is lost, and a result published while no submitter is subscribed is lost. The protocol's own machinery is the recovery: the submitter retries per Section 15, and the seen-set makes a repeat harmless. A deployment that needs broker-side retention needs a persistence layer this binding does not specify.¶
Two gates stand in series. The first is the broker's own authentication: broker authentication is REQUIRED by default -- a conforming receiver MUST NOT consume jobs over an unauthenticated broker connection, and the credential is supplied in configuration or in the broker URL's userinfo. A deployment MAY relax this by an explicit local setting (LOCAL), in which case the signature is the only gate and the deployment MUST NOT claim the two-gate posture. The second gate is the signature, unchanged. A required-auth NATS deployment is, for this reason, the more strongly gated of the bindings in this document: email's mailbox login authorizes the runner's reading, never the sender, so email stands on the signature alone.¶
The binding's limits are the broker's: payload sizes are bounded by the broker's maximum and by the cap of Section 5, and ordering is per-subject arrival only.¶
Of the two security properties of Section 5, broker authentication satisfies neither: it authenticates a party to the broker, not the object to the runner, and the broker is an intermediary that can read what it carries. TLS on a broker connection, where a deployment uses it, protects a hop to the broker in the same sense as Section 5's mail TLS, and no further. The MAC therefore carries the authentication property on this binding, as on every binding, and the confidentiality property is carried by the per-job encryption of Section 16, where the sender applies it.¶
Subjects are part of the email binding (Section 5): they are how the receiver recognizes a job message in the mailbox.¶
Subjects are matched case-insensitively. A preamble such as Re: MAY appear before the marker. The marker need not be the first character of the subject.¶
A job subject matches this pattern, in the sense of a search, not a full-string match:¶
[HOMING-PIGEON] (SP word)* SP job SP job-id¶
word is one or more visible characters. job-id is one or more visible characters with no space, further constrained below. The words between the marker and job are decoration. TEST is a legal decoration.¶
A result subject matches:¶
[HOMING-PIGEON] SP result SP job-id¶
ABNF [RFC5234] for the identifier only:¶
job-id = 1*128 job-id-char job-id-char = ALPHA / DIGIT / "." / "_" / "-"¶
The job_id in the subject MUST equal the job_id member. A mismatch MUST be rejected (Section 14).¶
job-id MUST NOT be . or ...¶
A UUID [RFC9562] is RECOMMENDED as the job_id.¶
A message whose subject is not a job subject is not a job. The receiver MUST NOT execute it. The receiver MAY mark it seen so that a later poll skips it. Result messages use the result pattern above and are produced by the runner. They are not jobs.¶
The signed object is one JSON object [RFC8259]. The receiver ignores preamble and trailer. Verification uses the object that ends in the signature marker.¶
Line endings are normalized before both signing and verifying: every CRLF becomes LF, then every remaining CR becomes LF. Trailing whitespace after that normalization is not part of the signed octets.¶
The sender builds the object without sig, serializes it, normalizes line endings, computes the MAC over those octets (Section 7.1), and replaces the final } with:¶
,"sig":"<96 hex digits>"}¶
JSON member order before sig is the sender's choice. sig MUST be last. Senders SHOULD serialize with no insignificant whitespace. Receivers MUST accept insignificant whitespace inside the object, because the MAC covers the octets the sender actually produced, not a re-encoding.¶
The receiver finds the signature as follows:¶
Normalize line endings and remove trailing whitespace.¶
Let marker be the last occurrence of the eight characters , " s i g " : ".¶
If marker is absent, the message is malformed.¶
The 96 characters after marker MUST be hexadecimal. The two characters after those MUST be " and }.¶
The signed octets are the bytes from the { that opens this object through the character before marker, plus one }.¶
That { is the start of the object that contains the marker, not a { inside a string. If no such { exists, the message is malformed.¶
A sender MUST NOT place the character sequence ,"sig":" inside any string value. The receiver uses the last occurrence.¶
The MAC is HMAC-SHA-384 [RFC2104] using SHA-384 [FIPS180-4].¶
The HMAC key is the UTF-8 octets of the shared secret. Implementations MUST NOT hash, stretch, or otherwise derive that key. The secret is a string of characters. Two sides that disagree on the characters disagree on the key.¶
sig is the 48-octet HMAC value encoded as 96 hexadecimal digits. Senders MUST use lowercase digits. Receivers MUST compare in constant time after mapping ASCII A-F to a-f.¶
Receivers MUST reject a malformed tail. Receivers MUST reject a MAC mismatch. A mismatch produces no result message (Section 14).¶
The compare covers all 96 digits. It MUST NOT stop at the first differing digit in a way that a network observer can time. A local reject log MAY record that the MAC failed. It SHOULD NOT record the expected digest.¶
A receiver MUST ignore object members it does not implement, except:¶
v other than 1 MUST be rejected¶
type other than job, on a message the receiver is executing as a job, MUST be rejected¶
kind other than exec or ping MUST NOT be executed; the reference receiver rejects any other value except sensor_alert, which it files as a signed alert for its Oracle (PROTOCOL.md, "Senses") and never executes¶
Ignoring a member means the member is still covered by the MAC and has no effect on version 1 processing.¶
| Member | JSON type | Presence | Rule |
|---|---|---|---|
v
|
number | REQUIRED | Integer 1. No other value is defined. |
type
|
string | REQUIRED |
job or result. |
job_id
|
string | REQUIRED | Section 6. |
key_id
|
string | OPTIONAL | Selects the HMAC key. Absent selects the default secret. |
aud
|
string | OPTIONAL | Audience claim. Enforcement is LOCAL. runner_id is accepted as its older spelling; when both are present aud is the claim. |
sig
|
string | REQUIRED | Section 7. Not part of the signed octets. |
key_id is covered by the MAC. Key identifiers MAY appear in logs. An unknown key_id MUST be rejected before the MAC is computed. The receiver MUST NOT try another secret.¶
If a receiver enforces runner_id, the expected value MUST come from the receiver's configuration. The signed value is what the submitter claimed. It is not the source of the expected value.¶
The receiver holds a map from key_id to secret. The empty identifier names the default secret.¶
Selection, before the MAC:¶
If key_id is absent, select the default secret.¶
If key_id is present and is a string in the map, select that secret.¶
Otherwise reject. Do not compute a MAC.¶
How keys are stored is LOCAL. Two values MUST NOT be assigned to one key_id. The default identifier has one slot.¶
A secret MAY be named by its fingerprint. The reference software does so. The fingerprint is k- followed by the first eight lowercase hexadecimal digits of SHA-384 over the UTF-8 octets of the label homing-pigeon key id v1\n and then the secret's UTF-8 octets:¶
key_id = "k-" ||
hex(SHA-384("homing-pigeon key id v1\n" || secret))[:8]
¶
The reference receiver puts that identifier in the map of Section 10, pointing at the same secret the empty identifier names. An operator-chosen name, when one was configured, is a further identifier for that same secret. One key_id still names one secret. One secret MAY have several identifiers. Two secrets MUST NOT share one.¶
The reference sender includes key_id on every message it signs. The value is the fingerprint, unless the sender was told to use a chosen name. The empty identifier is how a sender asks for the unnamed form. A receiver that has not registered the fingerprint MUST reject that message as an unknown key_id (step 3). An absent key_id still selects the default secret.¶
A fingerprint that is not in the map is an unknown key_id. So is a near-miss: a different length, a different case, or the fingerprint of a secret the receiver does not hold. The receiver MUST NOT select the default secret for any of those.¶
The name is a label. It is 32 bits of a one-way function over a secret of at least 128 bits of entropy (Section 17.3). It is not invertible. As an oracle for a guessed secret it is weaker than the HMAC on any captured message, which already tests a guess in full. The label keeps this hash apart from that HMAC and from the HKDF that derives a job key, so a name cannot be confused with a signature or turned into one. Publishing the name discloses nothing a signed message does not already disclose.¶
The DTLS PSK identity (Section 3) is a different label. homing-pigeon names the default secret. homing-pigeon: followed by a configured key_id names an additional secret. The fingerprint is not a PSK identity unless the operator stored that string as a key_id.¶
type is job.¶
{
"v": 1,
"type": "job",
"job_id": "3f9a1c2e-7b14-4e0a-9c2d-6f0a1b2c3d4e",
"issued_at": "2026-10-13T12:00:00Z",
"kind": "exec",
"script": "date\n",
"timeout_sec": 60,
"key_id": "submitter-a",
"reply_to": "submitter@example.com",
"sig": "001122...eeff"
}
¶
The sig line above is abbreviated. On the wire it is 96 hex digits.¶
| Member | JSON type | Presence | Rule |
|---|---|---|---|
issued_at
|
string | REQUIRED | Section 11.1. |
kind
|
string | REQUIRED |
exec or ping. |
reply_to
|
string | OPTIONAL | Section 11.2. |
script
|
string | see 11.3 | Inline script. exec only. |
script_url
|
string | see 11.3 |
https URL of the script. exec only. |
script_sha384
|
string | OPTIONAL | 96 lowercase hex digits over the script octets. |
interpreter
|
array of strings | OPTIONAL | Program, then its arguments. |
args
|
array of strings | OPTIONAL | Appended after the script path. |
env
|
object | OPTIONAL | String values merged into the environment. |
files
|
array | OPTIONAL | Section 11.4. |
timeout_sec
|
number | OPTIONAL | Positive integer. RECOMMENDED default 300. RECOMMENDED maximum 3600. |
issued_at is an ISO 8601 timestamp. It MUST carry a timezone designator, Z or a numeric offset. A value without a timezone MUST be rejected. A value that does not parse MUST be rejected. Senders SHOULD use UTC in the form YYYY-MM-DDTHH:MM:SSZ.¶
Let age be the runner's clock minus issued_at, in seconds.¶
The receiver MUST reject the job if age is greater than its past window. The RECOMMENDED past window is 3600 seconds. That window is LOCAL.¶
The receiver MUST reject the job if issued_at is too far in the future. The RECOMMENDED future allowance is 300 seconds.¶
reply_to is a single address, at most 254 characters, of the form local@domain, with no display name, no angle brackets, and no comma. The domain contains a dot.¶
If the member is absent, or present but invalid, the result goes to the runner's own mailbox. The job is not rejected for an invalid reply_to.¶
The receiver MUST take the result address only from this member. The email From header and the email Reply-To header MUST NOT select it.¶
kind ping carries no script. The runner MUST NOT start a script. The result has kind ping.¶
kind exec requires at least one of script or script_url. If neither is present, the receiver MUST reject the job (Section 12). If both are present, the receiver MUST use script_url and MUST ignore script.¶
If script_sha384 is present, the receiver MUST hash the script octets with SHA-384 and MUST reject the job if the digest differs. The digest is 96 lowercase hex digits.¶
If interpreter is absent, the runner uses a LOCAL default.¶
The script is written to a new file in a fresh directory for this job_id and run as:¶
<interpreter...> <script-file> <args...>¶
The working directory is that job directory. On timeout the receiver MUST kill the process tree. A timeout_sec above the receiver's maximum MUST be reduced to that maximum; the job MUST NOT be rejected for an excessive timeout alone. The RECOMMENDED maximum is 3600.¶
Each element is an object:¶
| Member | JSON type | Presence | Rule |
|---|---|---|---|
name
|
string | REQUIRED | File name inside the job directory. |
url
|
string | REQUIRED |
https URL. |
sha384
|
string | OPTIONAL | 96 lowercase hex digits. |
name MUST NOT be absolute, MUST NOT contain .. as a path segment, and MUST NOT escape the job directory. A violation MUST be rejected before the file is used.¶
Downloads MUST use HTTPS. If sha384 is present, a digest mismatch MUST be rejected.¶
Unknown kind MUST NOT be executed.¶
A job the receiver rejects after the MAC has verified, because the kind, the script hash, the file name, or a similar check failed, MUST produce a result (Section 13) with exit_code -2 and a short reason in stderr. That job MUST be marked seen and MUST NOT be retried.¶
A job rejected before the MAC verifies produces no result. See Section 14.¶
The result is signed with the same secret that verified the job. key_id on the result, when present, MUST name that secret. The reference sender includes it (Section 10.1): the identifier the job carried, or, when the job carried none, the default secret's fingerprint.¶
type is result. The subject is the result pattern in Section 6.¶
{
"v": 1,
"type": "result",
"job_id": "3f9a1c2e-7b14-4e0a-9c2d-6f0a1b2c3d4e",
"kind": "exec",
"completed_at": "2026-10-13T12:01:23Z",
"exit_code": 0,
"timed_out": false,
"stdout": "",
"stderr": "",
"stdout_truncated": false,
"stderr_truncated": false,
"duration_sec": 12.345,
"runner": "host.example",
"sig": "001122...eeff"
}
¶
| Member | JSON type | Presence | Rule |
|---|---|---|---|
completed_at
|
string | REQUIRED | Same form as issued_at. |
kind
|
string | REQUIRED | The job's kind. |
exit_code
|
number | REQUIRED | Section 13.1. |
timed_out
|
boolean | REQUIRED | True if the process tree was killed for time. |
stdout
|
string | REQUIRED | Captured standard output. May be empty. |
stderr
|
string | REQUIRED | Captured standard error. May be empty. |
stdout_truncated
|
boolean | REQUIRED | True if stdout was cut. |
stderr_truncated
|
boolean | REQUIRED | True if stderr was cut. |
duration_sec
|
number | REQUIRED | Seconds, as a JSON number. |
runner
|
string | REQUIRED | Host label. Authenticated only by the result MAC. |
| Value | Meaning |
|---|---|
| 0 | The process exited 0, or ping succeeded. |
| -1 |
timed_out is true. The process tree was killed. |
| -2 | Rejected before execution. Reason in stderr. Not retried. |
| other | The process exit status. |
If timed_out is true, exit_code MUST be -1.¶
Each stream MAY be cut at a LOCAL limit on the decoded text. The RECOMMENDED limit is 100000 characters. When a stream is cut, its *_truncated member MUST be true. When it is not cut, that member MUST be false.¶
A result specified here includes the output the receiver chose to send. Omitting output for a local privacy policy is outside version 1. A submitter MUST NOT assume output will be withheld. Where a disclosure policy is layered on the exchange (Section 1), this guidance does not hold and the submitter MUST NOT rely on it.¶
The receiver maintains a seen-set. The key SHOULD be job_id prefixed by the receiver's configured runner_id when one is configured, so two runners sharing a mailbox do not treat each other's jobs as replays.¶
For each unseen message:¶
If the subject is not a job subject, do not execute. This is not an error. Stop.¶
If the seen-set already contains this job_id, do not execute. Stop.¶
If the raw message exceeds the size cap (Section 5), reject with no result. Mark seen. Stop.¶
Select the key (Section 10). On failure, reject with no result. Mark seen. Stop.¶
Verify the MAC (Section 7). On failure, reject with no result. Mark seen. Stop. The From header is not an input to this step.¶
Parse the signed octets as one JSON object. If it is not an object, reject with no result. Mark seen. Stop.¶
If v is not 1 or type is not job, reject with no result. Mark seen. Stop.¶
If the subject job_id and the member job_id differ, reject with no result. Mark seen. Stop. (A job_id that is illegal under Section 6 is a shape failure of step 12: the job verified, so it is answered with an exit_code -2 result and enters the seen-set.)¶
Check issued_at (Section 11.1). On failure, reject with no result. Mark seen. Stop.¶
LOCAL checks (audience, per-key policy) MAY reject here. A reject at this step MUST NOT execute the job. Whether a result is sent is LOCAL. The reference receiver sends no result for an audience failure.¶
If kind is ping, go to step 13.¶
If kind is exec, resolve the script and files (Section 11). On failure, send a result with exit_code -2, mark seen, stop. If kind is anything else, send a result with exit_code -2, mark seen, stop.¶
Execute. Honor timeout_sec.¶
Build the result (Section 13) and sign it with the secret from step 4.¶
Stash the signed result in a LOCAL outbox, record job_id in the seen-set, and mark seen, in that order, before any send is attempted.¶
Send the result to reply_to if that member is valid, otherwise to the runner mailbox. If the send fails, the stashed result is resent on later polls, the same octets each time, until it lands or the stash's retention lapses. The job is never executed again (Section 15).¶
If the object verified at step 5 is an encryption envelope, Section 16 applies: decrypt after step 5 and process the recovered body from step 4. The envelope's verification never substitutes for the body's own.¶
The email From address MAY be logged after step 5. It MUST NOT authorize the job. It MUST NOT choose the result address.¶
Execution is at most once; delivery of the result is at least once.¶
A receiver MUST record job_id in the seen-set once it holds a signed result for the job (step 15), before the first send, and MUST NOT execute the job again because a send failed. A send that fails is retried from the stash; the resent result is the same octets.¶
Submitters MUST still treat exec as repeatable: a receiver whose seen-set was cleared, or a job delivered to two receivers, runs it twice.¶
Two senses of "seen" are kept apart. Marking a message seen (Section 14, steps 3 through 12 and 15) is the binding-level flag: that copy of the message is not reprocessed. Recording a job_id in the seen-set is the replay state of Section 14, and a receiver MUST add a job_id to the seen-set only after it has produced a result for it: the stashed result of step 15, or the exit_code -2 result of step 12. A reject that produces no result (steps 3 through 9, and step 10 when the LOCAL choice is silence) MUST NOT enter the seen-set. A redelivered copy of such a message is therefore processed afresh and rejected afresh. This is deliberate: a message that never verified cannot burn a job_id, so a forger holding a victim's job_id cannot block the genuine job from running.¶
This section defines per-job encryption: encrypting the job object itself, so the binding's store-and-forward hops carry ciphertext instead of the signed body. It is a required part of version 1. Implementations MUST support receiving and decrypting objects encrypted as this section specifies. Senders SHOULD encrypt; the reference software encrypts by default on the sending side, and encryption MUST NOT fail silently into cleartext. A receiver MUST NOT execute a job object received in cleartext unless the operator has explicitly enabled cleartext acceptance on that receiver. In the reference software the enable is a command-line parameter (--allow-cleartext), deliberately not a configuration setting, so the posture holds only for the invocation that asks for it; the sender's corresponding explicit choice is the reference sender's --cleartext parameter. The MAC of Section 7 is verified before the form of the object is considered, in every posture: cleartext is never unauthenticated.¶
This section is the protocol's confidentiality mechanism at the object layer. Receiver support is REQUIRED on every binding, because a sender MAY apply this section on any binding. Sender application is OPTIONAL on a binding that itself provides the confidentiality property as Section 5 states; the DTLS binding of Section 19 is that case (Section 19.4). On a binding that does not provide the property, this section is the only mechanism that does, and the sender posture above applies in full.¶
The plaintext is the complete signed job body in its Section 7 form — the payload object with the signature spliced in. The cipher is AES-256-GCM [SP800-38D]:¶
Key: HKDF [RFC5869] with HMAC-SHA-384. The input keying material is the UTF-8 octets of the secret selected per Section 10. The salt is the nonce. The info string is "homing-pigeon job-encryption v1". The output is 32 octets.¶
Nonce: 12 octets, freshly random for each message.¶
Additional authenticated data: the job_id, the key_id (empty for the default secret), and the algorithm string, each UTF-8, joined by single NUL (0x00) octets, in that order.¶
The transmitted body is a JSON envelope object, signed exactly as Section 7 specifies — the MAC covers the envelope as transmitted:¶
{"hp_enc": {"alg": "aes-256-gcm/hkdf-sha384",
"nonce": "<base64>", "ct": "<base64>"}}
¶
ct is the ciphertext with the 16-octet GCM tag appended, base64-encoded. When the selected secret is not the default, the envelope carries the same key_id member a cleartext body would. hp_enc is a reserved member name: a body whose members are exactly the envelope's is an envelope, and senders MUST NOT use hp_enc for any other purpose.¶
An envelope is processed inside Section 14's steps, not beside them:¶
The envelope's key_id selects the secret (Section 10), exactly as for a cleartext body.¶
The envelope's MAC is verified — step 5 applied to the envelope. On failure, reject as step 5 specifies. A receiver MUST NOT attempt decryption before the MAC verifies.¶
Decrypt. Any failure — an unknown alg, a malformed member, a tag mismatch — is a rejection with no result, marked seen, as in step 5. No partial plaintext is processed.¶
The recovered body re-enters Section 14 at step 4, as if it had arrived directly; its own MAC is verified again, and its key_id MUST match the envelope's.¶
Support of this section is REQUIRED (Section 16). A receiver that does not implement it is not conformant; presented with an envelope, it sees an object without v or type and rejects it at step 7.¶
The result for an encrypted job SHOULD be encrypted in the same construction, with the job's job_id in the additional data, so an exchange is encrypted in both directions or neither. A receiver's local policy MAY override in either direction; the reference receiver exposes that policy as configuration and defaults to mirroring the job.¶
Per-job encryption provides confidentiality against observers of the transport. It provides NO forward secrecy, and the two statements travel together wherever the mechanism is described: the key derives from the static shared secret, so compromise of the secret exposes every recorded encrypted job and result, past and future. Encryption also raises the value of a captured secret under Section 17.3's analysis; it does not lower it.¶
A valid MAC is permission to run as the runner's account. Anyone who holds the secret can operate the host. This protocol does not define a sandbox. LOCAL confinement MUST NOT be assumed by the submitter.¶
HMAC authenticates; it does not encrypt. The protocol's confidentiality is Section 16, and nothing else in this document provides it. Where cleartext is in use under an operator's explicit enable (Section 16), the transport operator (in this binding, the mailbox provider) can read every job and every result, and an observer who can read the transport learns the script, the arguments, the environment, and the output.¶
With per-job encryption in use, the job and result bodies are encrypted end to end on the binding: an observer of the transport learns the subject (which names the job_id), the timing, and the size, and nothing of the content. The reference software encrypts by default. A receiver that has not explicitly enabled cleartext acceptance refuses a cleartext object; an enabled receiver logs each cleartext arrival, so the choice is visible.¶
Per-job encryption provides NO forward secrecy. Its key derives from the static shared secret, so compromise of the secret exposes every recorded encrypted job and result, past and future. A confidentiality claim for this protocol MUST be read with that limit attached.¶
One captured message is enough to test candidate secrets. More messages do not reduce that search. HMAC-SHA-384 does not yield the key from the tags.¶
The secret SHOULD be 32 octets from a cryptographic generator, stored as characters and used as UTF-8. Sixty-four hexadecimal characters are the usual form. A long human sentence is not a substitute. The secret SHOULD NOT be the mailbox password.¶
The floor is normative for this protocol: the shared secret MUST carry at least 128 bits of entropy, estimated as the secret's length times log2 of its alphabet size, and a sender MUST refuse to sign with a secret below that floor. The requirement answers the offline-guessing attack above, and it binds version 1 today: the reference runner refuses to start with a secret below the floor, and the reference sender refuses to sign with one.¶
From, Reply-To, Sender, and To MUST NOT satisfy the MAC and MUST NOT select reply_to. An invalid MAC MUST NOT cause any result to be sent.¶
Failing closed before the MAC stops a message from verifying under the default secret because of a typo, and it avoids using MAC time as a probe of which identifiers exist. A near-miss fingerprint is this case (Section 10.1): it is not the default secret under another spelling.¶
The past window rejects stale issued_at. The seen-set rejects a repeated job_id. A receiver with a wrong clock will accept stale jobs or reject fresh ones. A receiver whose seen-set is cleared will accept a replay that is still inside the past window. Jobs MUST be safe to repeat for that reason; a lost result no longer causes one (Section 15).¶
The transport credentials and the HMAC secret, which authorizes jobs.¶
The splitter uses the last ,"sig":". A string value that contains that sequence will not verify as the sender intended. Senders MUST keep that sequence out of values.¶
Version 1 results carry stdout and stderr up to the truncation limit. A receiver that wants to withhold output is implementing a different policy. That policy is not signaled by v. How that layering relates to this document is stated in Section 1.¶
An attacker who can drop the result mail delays delivery: the receiver resends the stashed result, the same octets, and does not execute the job again (Section 15). This protocol does not add a separate ack.¶
This document has no IANA actions.¶
No new port, service name, media type, TLS extension, or object identifier is required. HMAC-SHA-384 is already named by [RFC2104] and [FIPS180-4]. The algorithm OID hmacWithSHA384 (1.2.840.113549.2.10) already exists. This protocol identifies the algorithm by name, not by OID.¶
The reference port for the DTLS binding (Section 19), UDP 20220, is a LOCAL default of the reference implementation, not a registered assignment; deployments may use any port, and submitters learn the port out of band, with the address and the secret.¶
This section and the three that follow it specify one further binding of version 1 and one behavior on it: a direct DTLS 1.3 session between submitter and runner, with the shared secret as the session's pre-shared key (this section), and the session greeting the runner sends once the handshake has authenticated the submitter (Section 20). The messages do not change on this binding; everything this document specifies for them applies unchanged.¶
The store-and-forward bindings need no path between submitter and runner: a mailbox both can reach is enough, and the runner opens no listener. That is the right default, and it is also a ceiling. Where a direct path does exist -- the submitter can open a UDP flow to the runner's host -- a session buys three things the mailbox cannot: latency measured in round trips instead of poll cycles, confidentiality of the job and result objects themselves (not merely of one mail hop), and that confidentiality with forward secrecy.¶
The cost is real, and it falls on the submitter. The store-and-forward bindings ask the submitter only to produce a signed object and hand it to a store-and-forward system it already speaks. This binding asks the submitter to terminate DTLS itself: the handshake, retransmission timers, connection IDs, session resumption -- a network stack obligation, discharged in the submitter's own environment. Many submitter environments cannot open a UDP socket at all. That is why this binding is optional, why the greeting (Section 20) hangs on the session existing, and why the email binding remains the binding every submitter reaches. Section 21 states the conformance burden plainly.¶
Two consequences of a direct binding deserve emphasis up front. First, the runner listens: enabling this binding opens an inbound UDP listener on the runner's host, a posture the store-and-forward bindings deliberately never take. The binding is therefore off unless an owner enables it; nothing about installing or running the runner opens it. Second, channel security is not delivery. A session protects what crosses it while it lives; it acknowledges nothing at the protocol layer. Delivery, retry, and deduplication remain the protocol's own machinery (Sections 19.3 and 22.5).¶
The submitter initiates. The runner listens on a UDP port (LOCAL; the reference default is 20220, which is not registered -- see Section 18) only when the binding is enabled. One session carries any number of job and result messages in each direction while it lives. Roles do not reverse: the runner never initiates a session to a submitter.¶
This binding is a bounded profile of DTLS 1.3 [RFC9147]. An implementation of this binding implements exactly this profile; a peer proposing anything outside it is refused, and the refusal ends that handshake, not the listener.¶
Transport: DTLS 1.3 [RFC9147] over UDP. DTLS 1.2 MUST NOT be negotiated.¶
Ciphersuite: TLS_AES_256_GCM_SHA384 [RFC8446] only. No other suite is offered or accepted.¶
Authentication: pre-shared key only. The external PSK is the shared secret, under the identities of Section 3. Certificates are not part of this profile, in either direction. The shared secret MUST meet the minimum-entropy floor of this protocol (Section 17.3); a secret below the floor MUST NOT be offered as a PSK.¶
Key exchange: psk_dhe_ke only. The psk_key_exchange_modes extension MUST offer mode 1 and MUST NOT offer mode 0 (psk_ke); a responder MUST refuse a handshake that does not offer psk_dhe_ke. An ephemeral X25519 [RFC7748] key share MUST ride every handshake, full or resumed. Section 22.1 gives the reason: this is the profile's perfect-forward-secrecy requirement, and it is not negotiable within the profile.¶
Address validation: the responder MUST perform the HelloRetryRequest cookie exchange of [RFC9147] before allocating session state, and MUST NOT send any flight larger than the retry itself before the cookie returns. The cookie is stateless and is bound to the initiator's address.¶
Connection ID: both sides MUST negotiate the connection_id extension [RFC9146] and MUST accept records under the negotiated IDs from a changed source address, so that a NAT rebinding does not kill a session.¶
Retransmission: handshake flights are retransmitted on the timers [RFC9147] specifies. Duplicate or retransmitted handshake messages are consumed exactly once per handshake; a duplicate MUST NOT corrupt the transcript or the session.¶
Early data: none. 0-RTT MUST NOT be offered, sent, or accepted; epoch 1 keys are never derived.¶
Resumption: a responder MAY issue a NewSessionTicket. A resumed session MUST use a fresh ephemeral key share (the psk_dhe_ke rule admits no exception for resumption), MUST be treated as a new session for greeting purposes (Section 20), and the ticket SHOULD be single-use.¶
KeyUpdate: an implementation MUST NOT initiate KeyUpdate. An implementation that receives a KeyUpdate with update_requested set SHOULD answer it as [RFC9147] requires; an implementation MAY instead ignore KeyUpdate, and peers MUST NOT depend on it, as this profile never initiates one.¶
Malformed input: a malformed or undecryptable datagram is dropped and logged. It MUST NOT be fatal to the listener or to any session but, at most, its own.¶
Inside a session, application data is a stream of messages, each framed by a four-octet big-endian length prefix. Each message is one protocol envelope in the same form the store-and-forward bindings carry: the subject line, a blank line, then the signed body, exactly as the email binding carries it. A job message is answered, in due course, by its result message on the same session.¶
Acknowledgement and deduplication are the protocol's own, unchanged: the runner's seen-set governs whether a job executes (Section 14), the submitter retries per Section 15, and DTLS record protection contributes neither an acknowledgement nor a duplicate suppression at the protocol layer. A result produced after its session has died cannot be delivered on that session; the submitter learns of the loss by timeout, re-asks on a fresh session, and the seen-set makes the repeat safe.¶
This binding provides the confidentiality property of Section 5 itself: the session is direct between the holders of the shared secret, no intermediary stores the object or can read it, and the session AEAD protects the object in transit with forward secrecy (Section 22.1). Application of the per-job encryption of Section 16 (the hp_enc envelope) on this binding is therefore OPTIONAL. A sender may send a cleartext signed job inside a session; the envelope remains available where protection at rest, beyond the session's lifetime, is wanted (the reference sender applies it by default and offers --cleartext). The receiver rule of Section 16 is unchanged on this binding: a cleartext object is executed only where the operator has explicitly enabled cleartext acceptance. Receiver support of Section 16 remains REQUIRED, on this binding as on every binding. Neither layer's presence relaxes the other's checks where it is applied: the envelope signature is verified before decryption, and the object signature is verified before execution, session or no session. The session's authentication of its peer does not substitute for the object signature: authentication attaches to the object (Section 5), not to the transport, on this binding as on every other.¶
When a session's handshake completes -- the responder has verified the initiator's Finished and the session keys are live -- the RUNNER sends the session's first application message: the greeting. Three properties are normative.¶
The greeting is post-authentication, never before. A responder MUST NOT send it before the handshake completes, and an unauthenticated peer harvests nothing from this binding: before authentication the only thing a stranger can obtain is the cookie exchange of Section 19.2.¶
The greeting precedes work. A runner MUST NOT accept a job on a session before it has sent the greeting on that session.¶
The greeting is the responder's choice. Its disclosure tier is the runner's LOCAL configuration. The initiator cannot request, negotiate, or widen it; this document defines no mechanism by which it could.¶
The greeting is a JSON object [RFC8259] in the protocol's idiom:¶
{"disclosure": "family",
"platform": {"os_family": "linux",
"default_interpreter": ["sh"],
"protocol_versions": [1]},
"transport": "dtls",
"type": "greeting",
"v": 1}
¶
The members type (always greeting), transport (always dtls), v (the protocol version, 1), and disclosure (the tier in force) are always present. The platform member is present unless the tier is none, and its members depend on the tier:¶
none: no platform member. The greeting still goes out; it says the session exists and nothing about the host.¶
family: os_family (one of windows, macos, linux, other), default_interpreter (the argv the receiver runs a script with when the job names none) and protocol_versions (the message versions it opens). This is the reference default.¶
standard: family plus os_release (the operating system's release string) and python_version (the runner runtime's version).¶
full: standard plus path_tools, an object naming a fixed, bounded set of common tools and whether each is on the runner's PATH.¶
The platform values above are strings on the wire; a future enterprise profile MAY map them under an OID arc.¶
The disclosure is proportioned the way the protocol already proportions result content: a deployment that answers jobs with full output discloses more in results than one that answers with attestations, and the same owner's posture, expressed as a tier, governs the greeting. The information itself is of the kind any job the runner accepts could observe by running; the greeting gives it to the authenticated submitter up front, where it is useful for choosing what to send (an interpreter name, a script form), without spending a job to learn it.¶
A resumed session is a new session for this section: the greeting MUST be sent again, under the tier in force at that time.¶
This section is candid because the Introduction's promise -- that a submitter needs nothing but the specification and tools it already has -- does not survive contact with a session binding, and a specification that hides its costs invites broken deployments.¶
A conforming submitter for this binding implements DTLS 1.3 in the profile of Section 19.2 for itself: the handshake state machine, PSK binders, the cookie exchange, handshake retransmission timers (a UDP peer that cannot retransmit cannot finish a handshake on a lossy path), connection IDs, and resumption. It needs a UDP path to the runner, which is an environment property, not a code property: a submitter running inside a hosted tool that offers only HTTPS egress, or no raw sockets at all, cannot use this binding no matter how correct its implementation is.¶
None of this is required of a submitter that uses only the store-and-forward bindings. The conformance of the messages and their checks is untouched by this section, a runner without this binding is a complete deployment, and an owner enables the binding as an additional door on a host they choose, for submitters that can reach it. The bindings coexist: the same runner, the same secrets, the same objects; the session is a faster, confidential carriage for submitters equipped for it, and the mailbox remains for those that are not.¶
The profile's load-bearing rule is that psk_ke is prohibited. In psk_ke, session keys derive from the pre-shared key alone; anyone who later obtains the secret and has recorded the session decrypts it in full. Because this protocol's shared secret is long-lived and is also the job-signing key, that failure mode would be catastrophic and silent. In psk_dhe_ke, every session -- full or resumed -- mixes a fresh ephemeral X25519 secret into its keys, so a later compromise of the shared secret yields the ability to impersonate going forward (which the version 1 analysis already grants a secret-holder) but decrypts no recorded session. An implementation MUST NOT offer, accept, or fall back to psk_ke, including on resumption and including when a peer insists. One limit is stated plainly: forward secrecy here is not post-quantum secrecy. The ephemeral exchange is X25519, so a recorded session is exposed to a future cryptographically relevant quantum computer in a way the symmetric constructions of this protocol (the object MAC of Section 7.1 and the per-job encryption of Section 16) are not. A hybrid post-quantum key exchange is the known remedy and is future work for this profile.¶
The shared secret does two jobs on this binding: it is the DTLS external PSK, and it remains the HMAC key of the job and result objects. These are independent constructions (the TLS key schedule's HKDF derivation chain, and HMAC-SHA-384 over the object), and neither's outputs feed the other. The composition does not weaken the analysis of Section 17: the session authenticates the channel, the signature authorizes the object, and a job's authority comes from the signature alone, so a job captured anywhere -- a mailbox, a session recording -- is exactly as powerful as it is on any other binding, and replay is governed by the same seen-set and freshness rules. The offline-guessing analysis of Section 17.3 applies to the PSK unchanged: the entropy floor is normative for the secret in both roles.¶
The greeting is a deliberate, bounded disclosure to an authenticated peer. Its bounds are its defenses: it is never sent before authentication, it contains no identifiers beyond platform shape (no hostname, no addresses, no user names, no filesystem paths beyond tool presence), its richest tier names a fixed tool set rather than enumerating the system, and its tier is set by the party being described. An owner who considers even the OS family sensitive sets none and the greeting still satisfies Section 20. What the greeting must never become is a negotiation: a future extension that lets the initiator ask for more would invert the design and MUST NOT be added compatibly.¶
A UDP listener is a reflection surface. The cookie exchange is the mitigation this profile adopts: the responder is stateless and sends nothing larger than the retry flight until the initiator proves it can receive at its claimed address, so the binding cannot be used to amplify traffic at a third party beyond that small, fixed flight. The listener's other exposure is computational: handshakes cost more than datagrams. The stateless cookie keeps pre-authentication cost to one MAC verification per first hello; malformed datagrams are dropped at parse, and per-session failure is contained to the session.¶
Enabling this binding changes the host's posture from the store-and-forward bindings' no-listener stance, and this document does not minimize that: the listener is off unless enabled, its port is LOCAL, and an owner who does not need the session binding should leave it off and lose nothing.¶
DTLS authenticates and encrypts records; it does not promise that a job arrived, ran, or was answered. This profile deliberately keeps the protocol's at-least-once semantics (Section 19.3) rather than letting a reliable-looking channel imply reliable delivery: sessions die (reboots, NAT timeouts past the connection ID's rescue, key exhaustion), and when one dies mid-exchange the loss is visible as a timeout, recoverable by a retry the seen-set makes safe. An implementation MUST NOT treat session establishment as job acceptance, nor session delivery of a result as its acknowledgement; the signed result message is the acknowledgement, as on every binding.¶
The confidentiality claim is end to end between the submitter and the runner process. A middlebox that terminates DTLS -- an inspecting proxy the path forces traffic through -- ends the claim at that box: the session is then confidential in two hops, and the operator of the middlebox can read job and result objects in clear, though per-job encryption (Section 19.4) still protects an encrypted object past it. Deployments that route this binding through such a box should enable the object layer and understand which guarantee each layer is carrying.¶