<?xml version='1.0' encoding='utf-8'?>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     category="info"
     docName="draft-howard-virp-06"
     ipr="trust200902"
     submissionType="IETF"
     obsoletes=""
     updates=""
     xml:lang="en"
     version="3">

  <front>
    <title abbrev="VIRP">VIRP: Verified Infrastructure Response Protocol</title>
    <seriesInfo name="Internet-Draft" value="draft-howard-virp-06" stream="IETF"/>
    <author fullname="Nate Howard" initials="N." surname="Howard">
      <organization>Third Level IT LLC</organization>
      <address>
        <postal>
          <city>Allen Park</city>
          <region>MI</region>
          <country>United States of America</country>
        </postal>
        <email>nhoward@thirdlevelit.com</email>
        <uri>https://thirdlevel.ai</uri>
      </address>
    </author>
    <date year="2026" month="August" day="1"/>
    <area>Security</area>
    <workgroup>Internet Engineering Task Force</workgroup>
    <keyword>infrastructure</keyword>
    <keyword>attestation</keyword>
    <keyword>audit</keyword>
    <keyword>automation</keyword>

    <abstract>
      <t>The Verified Infrastructure Response Protocol (VIRP) defines a trust
      framework for operators -- human or autonomous -- acting on live network
      infrastructure. As operations shift toward agentic and automated systems
      that can autonomously configure, audit, and remediate production
      environments, the absence of a verifiable chain of custody for
      observations and actions introduces fundamental risks: fabricated
      telemetry, unauthorized state changes, and the inability to distinguish
      legitimate operations from compromise. VIRP routes every observation and
      every authorization decision through a designated collection-and-
      verification boundary that the requesting party does not control.
      Observations are authenticated at collection time using HMAC-SHA256; in
      session-bound mode (<xref target="obs-v2"/>) authentication uses a
      per-session key and binds the response to session, device, sequence, and a
      command-digest field. Validating the command binding additionally requires
      trusted request context from which the verifier recomputes the command
      digest. A two-channel architecture separates read-only Observation from
      write-intent Intent, and trust tiers (GREEN/YELLOW/RED/BLACK) govern
      action authorization with human-in-the-loop controls for elevated
      operations.</t>

      <t>VIRP's observation and chain-integrity guarantees are symmetric and
      explicitly scoped by key role; approval and federation records use
      asymmetric Ed25519 signatures. Distinct key roles authenticate
      observations, chain entries, intents, approvals, and federation records
      (<xref target="keys"/>), though the reference implementation currently
      reuses one key across the v1-observation and v2-derivation roles; a holder
      of a symmetric key can both verify and forge within that key's scope, so
      VIRP does not provide publicly verifiable observation origin and does not
      defend a record against an adversary holding the relevant key or
      controlling the collection boundary. Authentication does not certify that
      a response reflects the managed device's true state, only that the
      boundary obtained and authenticated those bytes for that recorded request.
      Asymmetric proof of origin and external anchoring of the chain are
      distinct, independent items of future work (<xref target="future"/>).</t>
    </abstract>
  </front>

  <middle>

    <section anchor="reqlang" numbered="true">
      <name>Requirements Language</name>
      <t>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
      <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when,
      they appear in all capitals, as shown here.</t>
    </section>

    <section anchor="intro" numbered="true">
      <name>Introduction and Motivation</name>
      <t>The deployment of AI agents on live infrastructure represents a
      fundamental shift in how networks are operated. Where previous automation
      relied on deterministic scripts with predictable outputs, modern AI agents
      operate with significant autonomy -- selecting commands, interpreting
      results, and initiating multi-step remediation sequences without
      per-action human approval.</t>

      <t>This capability creates an accountability gap. When a traditional
      monitoring system reports that a router's CPU is at 94%, the operator can
      trace that measurement to a specific SNMP poll at a specific timestamp
      against a specific OID. When an AI agent reports the same finding, the
      operator must trust that the agent actually queried the device, that the
      query returned what was reported, and that the agent's interpretation
      matches the raw data. None of these properties are currently
      verifiable.</t>

      <t>The consequences of unverifiable AI observations are significant. An
      agent that fabricates or replays telemetry can drive incorrect
      remediation. An agent that takes undocumented actions leaves no audit
      trail for compliance or incident response. An agent that is compromised --
      or that simply makes an error in a long autonomous loop -- produces
      outputs that are indistinguishable from legitimate operations.</t>

      <t>VIRP addresses this gap by establishing cryptographic evidence at every
      layer of the observation-to-action pipeline. The protocol is not concerned
      with what the AI agent is capable of doing. It is concerned with
      establishing what the agent actually did, when, against which device, and
      whether that action was authorized by the appropriate tier of human
      oversight.</t>

      <t>This revision states materially narrower security claims than
      draft-howard-virp-05 in several places. That narrowing is deliberate. It
      reconciles the specification with the reference implementation: earlier
      revisions described a per-observation Ed25519 signature and a
      two-tool machine-verified formal result, neither of which exists in the
      code. The corrected claims, and the reasoning behind each, are summarized
      in <xref target="changes"/>. Readers comparing this revision against -05
      should treat the removals as corrections, not as regressions in the
      protocol.</t>
    </section>

    <section anchor="trustproblem" numbered="true">
      <name>The Trust Problem in Agentic Infrastructure</name>

      <section numbered="true">
        <name>Current State</name>
        <t>Existing infrastructure management tools assume a trusted operator.
        SNMP polling trusts that the collector reports what it received. Ansible
        playbooks trust that tasks executed as written. SIEM correlation trusts
        that log sources are authentic. These trust assumptions are reasonable
        when humans are in the loop at every step.</t>
        <t>AI agents break these assumptions. An agent running in an autonomous
        loop may execute hundreds of commands per session. Human review of every
        action is impractical. The operational benefit of AI autonomy is
        inseparable from reduced per-action oversight -- which means the
        integrity guarantees that relied on human review must be replaced with
        cryptographic equivalents. This shift is consistent with the Zero Trust
        Architecture principles articulated in <xref target="NIST-ZTA"/>: trust
        is established per-operation through verification, not inherited from an
        authenticated session.</t>
      </section>

      <section numbered="true">
        <name>The Fabrication Problem</name>
        <t>The most significant risk in AI-driven infrastructure management is
        not malicious action -- it is undetectable error. An AI agent that
        misreads output, hallucinates a device state, or generates
        plausible-looking but incorrect telemetry can drive remediation actions
        based on false premises. Without cryptographic evidence that an
        observation was collected from a real device at a real timestamp, there
        is no mechanism to distinguish a genuine observation from a fabricated
        one.</t>
      </section>

      <section numbered="true">
        <name>The Accountability Gap</name>
        <t>When an AI agent takes an action on infrastructure, current tooling
        records what the agent reported it did. VIRP records what the collection
        boundary observed the device return, with a chain of custody linking the
        intent to the observation to the outcome. This distinction is
        operationally significant: in an incident, the difference between what
        an agent said it did and what the device actually experienced may be the
        entire root cause.</t>
      </section>
    </section>

    <section anchor="overview" numbered="true">
      <name>Protocol Overview</name>
      <t>VIRP defines a layered protocol stack with four primary
      components:</t>

      <table anchor="fig-components">
        <name>Protocol Components</name>
        <thead>
          <tr><th>Component</th><th>Description</th></tr>
        </thead>
        <tbody>
          <tr>
            <td>O-Node</td>
            <td>Observation Node -- the collection-and-verification boundary.
            All device communication occurs within the O-Node. Observations are
            authenticated before leaving the O-Node. The authenticating key
            material is held inside that boundary.</td>
          </tr>
          <tr>
            <td>Observation Channel</td>
            <td>Read-only channel for device state collection. Observations
            carry an HMAC-SHA256 authentication code, sequence numbers, and
            timestamps. Sequence gaps indicate missing observations.</td>
          </tr>
          <tr>
            <td>Intent Channel</td>
            <td>Write-intent channel for action authorization. Intents must be
            filed before execution. The tier classifier evaluates intent against
            the trust tier framework before authorizing execution.</td>
          </tr>
          <tr>
            <td>Chain DB</td>
            <td>Append-only database of authenticated chain entries referencing
            observations. Forms the tamper-evident audit trail, under the
            key-scoping stated in <xref target="chainenc"/>.</td>
          </tr>
        </tbody>
      </table>

      <t>The O-Node operates as the collection boundary in the architecture. The
      requesting party -- an AI agent or a human-driven client -- is treated as
      a party outside that boundary: it may read authenticated observations for
      verification, but in the reference architecture it holds no
      observation-authentication, chain, or approval key and therefore cannot
      produce those records. This asymmetry is the foundation of VIRP's security
      model, and its exact scope is stated per key role in
      <xref target="keys"/>.</t>
    </section>

    <section anchor="primitives" numbered="true">
      <name>The Seven Trust Primitives</name>
      <t>VIRP defines seven trust primitives that collectively address the
      accountability gap in agentic infrastructure operations. Each primitive
      addresses a distinct failure mode.</t>

      <section numbered="true">
        <name>Primitive 1: Observation Integrity</name>
        <t>Every device observation is authenticated at collection time within
        the O-Node using HMAC-SHA256. In session-bound mode
        (<xref target="obs-v2"/>) the authenticated header covers the device
        identifier, timestamp, per-session sequence number, session identifier,
        a digest of the canonical command, and the payload length, and the
        authenticated bytes include the raw output. In the legacy format
        (<xref target="obs-v1"/>) the authenticated bytes cover the header and
        output but carry no session, device, or command binding. Observations
        that fail verification are rejected. A party that does not hold the
        relevant key cannot produce an observation that verifies; a party that
        does hold it can (<xref target="keys"/>).</t>
        <t>Failure mode addressed: fabricated or replayed telemetry originating
        outside the collection boundary. An agent that holds no
        observation-authentication key cannot report a device state that the
        boundary did not collect.</t>
      </section>

      <section numbered="true">
        <name>Primitive 2: Two-Channel Separation</name>
        <t>VIRP enforces strict separation between Observation (read) and Intent
        (write) channels. Observations flow from device to O-Node to chain.
        Intents flow from the requesting party to the tier classifier to the
        O-Node to the device. The channels are architecturally separate and are
        additionally bound to distinct key roles: the implementation refuses to
        authenticate an Observation Channel message with an Intent Channel key
        or the reverse.</t>
        <t>Failure mode addressed: an agent that reads device state and takes
        action within the same unmonitored channel, bypassing authorization
        controls.</t>
      </section>

      <section numbered="true">
        <name>Primitive 3: Intent Gating</name>
        <t>Before any write operation reaches a device, an Intent must be filed
        with the O-Node specifying the target device, command class, and
        justification. The tier classifier evaluates the intent against the
        trust tier framework. GREEN tier intents may execute immediately. YELLOW
        tier intents require human acknowledgment. RED tier intents require
        explicit approval carrying an Ed25519 signature from an enrolled
        approver, bound to the command digest, the target device, and an
        expiry. BLACK tier intents are rejected unconditionally. Commands that
        match no classification rule are treated as RED and are refused by
        default.</t>
        <t>Failure mode addressed: unauthorized state changes. An agent cannot
        modify device configuration without a prior authorized intent on
        record.</t>
      </section>

      <section numbered="true">
        <name>Primitive 4: Outcome Verification</name>
        <t>Following every authorized action, VIRP collects a post-action
        observation and compares the resulting device state against the expected
        outcome of the intent. Divergence between expected and actual state
        generates a trust tier escalation. Outcomes are authenticated and
        chained alongside the original intent.</t>
        <t>Failure mode addressed: actions that execute but produce unexpected
        results, which would otherwise be invisible in the audit trail.</t>
      </section>

      <section numbered="true">
        <name>Primitive 5: Baseline Memory</name>
        <t>VIRP maintains an authenticated baseline of known-good device state
        derived from a verified observation sweep. Subsequent observations are
        compared against the baseline. Deviations -- new BGP peers, changed
        interface states, modified firewall policies -- generate alerts and may
        trigger tier escalation. The baseline itself is a chain of authenticated
        observations and is verifiable by any holder of the relevant key.</t>
        <t>Failure mode addressed: undetected configuration drift. The system
        knows what normal looks like and can detect when it changes.</t>
      </section>

      <section numbered="true">
        <name>Primitive 6: Multi-Vendor Normalization</name>
        <t>VIRP defines a normalized observation schema that abstracts
        vendor-specific output formats. Cisco IOS show commands, FortiOS get
        commands, and Linux system calls are mapped to a common observation
        structure. This enables consistent chain integrity verification across
        heterogeneous infrastructure.</t>
        <t>Failure mode addressed: trust model fragmentation across vendors. A
        single chain can represent observations from any supported device
        type.</t>
      </section>

      <section numbered="true">
        <name>Primitive 7: Agent Containment</name>
        <t>The requesting process must be treated as an untrusted principal.
        Process containment must be enforced externally -- at the hypervisor
        layer, at the network boundary, and at the filesystem level. The
        requesting process must not have access to observation, chain, or
        approval key material, to device credentials, or to direct network paths
        to managed devices.</t>
        <t>This primitive was identified during live adversarial testing when
        the agent under test self-reported that it could bypass VIRP's
        observation chain by going around it entirely -- accessing credentials
        directly, connecting to devices without chain entries, or spawning
        unsupervised inference sessions. The primitive formalizes the
        architectural response: containment is not policy, it is structure.</t>
        <t>Failure mode addressed: a requesting process that circumvents the
        observation chain entirely. VIRP protects the chain -- Primitive 7
        ensures the requester cannot go around it.</t>
      </section>
    </section>

    <section anchor="crypto" numbered="true">
      <name>Cryptographic Architecture</name>

      <section anchor="keys" numbered="true">
        <name>Keys and Authentication Roles</name>
        <t>VIRP defines several authentication key roles, each authenticating a
        specific class of record. Most roles use distinct key material, but the
        reference implementation currently reuses the O-Key both for v1
        observation authentication and as the input to v2 session-key derivation
        (below). The channel and role separation that is enforced is structural,
        not policy: the implementation refuses to use, for example, an R-Key to
        authenticate an Observation Channel message.</t>

        <dl>
          <dt>O-Key (K_obs), HMAC-SHA256 <xref target="RFC2104"/> <xref target="RFC6234"/>:</dt>
          <dd>A static key with two roles: it directly authenticates legacy v1
          observation-channel messages (<xref target="obs-v1"/>), and it is the
          input keying material from which v2 session keys are derived. v1
          verification is a stateless HMAC check by a holder of the O-Key.
          Because HMAC is symmetric, an O-Key holder can both verify and produce
          v1 observations; because the O-Key is also the derivation input, an
          O-Key holder can additionally derive v2 session keys.</dd>

          <dt>R-Key (reasoning key), HMAC-SHA256:</dt>
          <dd>Authenticates Intent Channel messages -- the requests a reasoning
          or requesting party submits. Channel binding is enforced by the
          implementation: an R-Key can only authenticate Intent Channel messages
          and an O-Key can only authenticate Observation Channel messages; a
          violation is rejected. Authenticating an intent request is not
          authorizing it; the gate and approval policy remain authoritative. In
          the protocol model an R-Node holds the R-Key and uses it to
          authenticate Intent Channel messages. The current reference API and
          autopilot execution path does not use R-Key-authenticated Intent
          Channel messages; it submits framed requests directly to the O-Node
          socket. The statement "the requesting agent holds no key" is therefore
          accurate only for the current deployed path. The protocol architecture
          permits an R-Node to hold an R-Key that authenticates intent but never
          observations.</dd>

          <dt>Session key, HKDF-SHA256-derived:</dt>
          <dd>Authenticates session-bound v2 observations
          (<xref target="obs-v2"/>). Derived per session from the O-Key material
          and the authenticated session transcript. A party that cannot derive
          the session key cannot produce a valid v2 observation for that
          session.</dd>

          <dt>K_chain (chain key), HMAC-SHA256, distinct from K_obs:</dt>
          <dd>Authenticates trust-chain entries (<xref target="chainenc"/>). A
          holder of K_chain can rewrite chain history; a holder of only K_obs
          cannot.</dd>

          <dt>Approver key, Ed25519 <xref target="RFC8032"/> <xref target="ED25519"/>:</dt>
          <dd>Signs approval records in the intent-approval flow. The private
          key is held outside the collection boundary, so the boundary cannot
          mint its own approvals.</dd>

          <dt>Federation key, Ed25519:</dt>
          <dd>Authenticates federation material
          (<xref target="federation"/>).</dd>
        </dl>

        <t>The exact session-key derivation is as follows (HKDF-SHA256
        <xref target="RFC5869"/>, single block, 32-byte output):</t>

        <figure anchor="fig-hkdf">
          <name>Session Key Derivation</name>
          <artwork type="ascii-art"><![CDATA[
IKM  = the O-Key (master key material)
salt = the session transcript_hash (32 bytes)
info = the generation counter, big-endian u64 (exactly 8 octets)

PRK  = HMAC-SHA256(salt, IKM)
OKM  = HMAC-SHA256(PRK, info || 0x01)
session_key = OKM  (32 bytes)
]]></artwork>
        </figure>

        <t>These are the exact authenticated inputs; any mismatch changes the
        key and breaks interoperability. Implementations MUST NOT substitute a
        zero salt for a missing transcript: a session key derived without an
        authenticated transcript is a protocol error, and an implementation
        that cannot compute transcript_hash MUST fail the session rather than
        derive a key.</t>

        <t>The value <tt>generation</tt> is a derivation input. It MUST be
        carried in the authenticated SESSION_BIND message so that a separate
        verifier can re-derive the key from the authenticated transcript; see
        <xref target="obs-v2"/> for the normative rules, including the
        rollback-protection requirement. In the reference implementation
        <tt>generation</tt> is not yet transported; see
        <xref target="implstatus"/>.</t>

        <t>The session-derivation input keying material is the O-Key itself.
        There is no separate master key in the reference implementation. The
        O-Key serves two roles: it directly authenticates v1 observations and it
        is the HKDF input for v2 session keys. Consequently a holder of the
        O-Key can both forge v1 observations and, given the required derivation
        context, derive v2 session keys within its scope. This is a weaker
        separation than a dedicated derivation master would provide.
        Implementations SHOULD adopt a dedicated session-derivation master
        distinct from the legacy v1 authentication key; this document records
        the current reuse as a migration target, not as a designed
        property.</t>

        <t>The security consequence of role separation is important and easy to
        understate: possession of one key does not confer the ability to forge
        records in another key's scope -- with one current exception. It holds
        for K_chain, the R-Key, approver keys, and federation keys: a holder of
        any one cannot forge in another's scope. It does not currently hold
        between v1 authentication and v2 derivation, because both use the same
        O-Key material: an O-Key holder can forge v1 observations and derive v2
        session keys. A component holding only K_chain, or only an approver key,
        gains no observation-forging ability. The collection boundary may in a
        given deployment hold several of these keys at once, but that is a
        deployment fact, not an inherent property of any single key. The threat
        model (<xref target="threat"/>) is therefore stated per key, not per
        "the O-Key".</t>

        <t>Across all symmetric keys, VIRP provides integrity and origin
        authentication relative to the secrecy of the relevant key, held within
        the trusted boundary. It does not provide publicly verifiable origin for
        observations: any holder of the authenticating key can also produce
        records. The requesting party holds no observation, chain, or approval
        key. An R-Node may hold an R-Key and can therefore authenticate intent
        messages, but that key cannot authenticate observations, chain entries,
        or approvals, and authenticating an intent is not authorizing it.
        Asymmetric observation signing (<xref target="asym"/>) would add public
        verifiability and remove verifier forgery, but would not by itself
        defend against compromise of the signer; that requires hardware custody,
        independent witnessing, or external anchoring
        (<xref target="future"/>), which are separate mechanisms addressing a
        separate threat.</t>
      </section>

      <section anchor="custody" numbered="true">
        <name>Key Custody</name>
        <t>In the reference implementation, verification components load the
        relevant symmetric key and check records locally. Because symmetric
        verification is also a forging capability, every holder of a given key
        is part of that key's trust boundary; deployments SHOULD minimize the
        number of holders and MUST NOT treat a symmetric verifier as an
        untrusted party. A deployment that needs verification by a party which
        must be unable to forge cannot obtain that from the symmetric paths
        specified here; it requires the asymmetric work of
        <xref target="asym"/>, or a verification service that returns an
        accept/reject decision without releasing the key.</t>
        <t>The requesting agent holds no observation, chain, or approval key.
        Approver and federation keys are asymmetric; the approver's private key
        is held outside the collection boundary, which holds only the
        corresponding public key.</t>
      </section>

      <section anchor="obs-v1" numbered="true">
        <name>Legacy v1 Observation</name>
        <t>The v1 observation uses the static O-Key and an HMAC-SHA256 over the
        v1 message header and payload. Verification is stateless: a holder of
        the O-Key recomputes the HMAC and compares. v1 does not carry a session
        binding and does not by itself provide replay protection
        (<xref target="threat"/>), and it does not provide the request or
        command binding that v2 provides.</t>

        <t>Legacy v1 is a deprecated transitional format. New implementations
        MUST use v2 for device-evidence observations. Implementations MAY
        continue to emit v1 authenticated rejection and compatibility records
        during migration, but verifiers MUST NOT interpret a v1 record as
        session-bound or replay-resistant evidence.</t>

        <t>Stated plainly: the reference implementation currently defaults to
        v1. Request parsing defaults the observation version to 1; the Python
        API does not perform the v2 handshake or request v2; gate rejections are
        v1. v2 requires explicit session negotiation and an explicit version
        selection. Migrating the reference default to v2 is a tracked
        implementation task (<xref target="implstatus"/>), recorded here rather
        than claimed as complete.</t>

        <t>Deprecated does not mean unspecified. The v1 layout is:</t>

        <figure anchor="fig-v1">
          <name>v1 Observation Message Layout</name>
          <artwork type="ascii-art"><![CDATA[
 offset  size  field
  0       1    version = 1
  1       1    message type
  2       2    total message length, uint16 big-endian
  4       4    node_id, uint32 big-endian
  8       1    channel
  9       1    tier
 10       2    reserved, MUST be zero
 12       4    sequence, uint32 big-endian
 16       8    timestamp_ns, uint64 big-endian
 24      32    HMAC-SHA256
 56       1    observation type
 57       1    observation scope
 58       2    observation-data length, uint16 big-endian
 60       n    observation data
]]></artwork>
        </figure>

        <t>The HMAC input is bytes 0 through 23 concatenated with bytes 56
        through (total_length - 1); the HMAC field (bytes 24 through 55) is
        excluded from its own input.</t>

        <t>A conforming v1 verifier MUST apply the following validation rules
        before accepting a record:</t>
        <ul>
          <li>The <tt>version</tt> octet MUST equal 1 and the message type MUST
          be a defined value; unknown enumerated values MUST be rejected rather
          than ignored.</li>
          <li><tt>total message length</tt> MUST be at least 60 and MUST NOT
          exceed 65535. The maximum conforming observation-data length is
          therefore 65475 octets.</li>
          <li>The declared observation-data length MUST be consistent with
          <tt>total message length</tt>; a declared length exceeding the
          remaining input MUST be rejected.</li>
          <li>Octets after the declared fields MUST be rejected; a verifier MUST
          NOT accept trailing bytes.</li>
          <li>The <tt>reserved</tt> field MUST be zero on transmission and MUST
          be rejected if non-zero on receipt.</li>
          <li>The HMAC comparison MUST be constant-time.</li>
        </ul>
      </section>

      <section anchor="obs-v2" numbered="true">
        <name>Session-Bound v2 Observation</name>
        <t>The v2 observation is authenticated as
        <tt>HMAC-SHA256(session_key, serialized_header || payload)</tt>, where
        session_key is the HKDF-derived per-session key
        (<xref target="keys"/>). The fixed-width header carries a 64-bit node
        identifier; a nanosecond timestamp; a per-session sequence number; a
        session identifier; a 64-bit device identifier; a SHA-256 hash of the
        command (not the command string itself); and the payload length.</t>

        <t>Because the header binds session, sequence, device, and a command
        hash, a v2 observation authenticates a command digest within a specific
        session; it becomes evidence about a specific command only when a
        verifier possesses trusted command context whose canonical digest
        matches that field. A party that cannot derive the session key cannot
        forge a v2 observation for that session, which is a stronger scoping
        than v1's static-key model.</t>

        <t>The serialized header is exactly 88 octets, with no padding and all
        multi-byte fields in network byte order:</t>

        <figure anchor="fig-v2">
          <name>v2 Observation Header Layout</name>
          <artwork type="ascii-art"><![CDATA[
 offset  size  field
  0       1    version
  1       1    channel
  2       1    tier
  3       1    reserved (MUST be zero)
  4       8    node_id
 12       8    timestamp_ns   (nanoseconds since epoch)
 20       8    seq_num        (monotonic per session)
 28      16    session_id     (from SESSION_BIND)
 44       8    device_id      (stable 64-bit device identifier)
 52      32    command_hash   (SHA-256 of the canonical command)
 84       4    payload_len
 = 88 octets
]]></artwork>
        </figure>

        <t>The observation carries the command only as a SHA-256 hash, never as
        text. The verifier receives the expected command as a separate input
        (trusted request context), canonicalizes it, hashes it, and compares
        against the authenticated command_hash. The full command text is not in
        the authenticated bytes, not in the base observation, and not reliably
        in the chain entry; a consuming API that reattaches the command string
        does so as convenience metadata outside the authenticated bytes.</t>

        <t>The consequence must be stated plainly: a base v2 observation is not
        a self-contained receipt of the command. It binds a command only
        relative to a relying party that already possesses the correct request
        context. A party that keeps the observation but loses the trusted
        request record retains a valid authentication over a digest whose
        preimage it can no longer establish. Closing this -- authenticating
        either the complete canonical typed-operation encoding, or a digest of a
        separately authenticated request record carrying operation identifier,
        registry version, parameters, and resolved target -- is the subject of
        <xref target="typedops"/> and is the strongest argument for a typed
        operation model.</t>

        <t>The header layout can represent an all-zero session identifier, but
        no bootstrap v2 observation uses one: the v2 signer requires an active
        session and copies the active session identifier, and error records fall
        back to v1. A conforming v2 device-evidence observation MUST contain a
        non-zero session identifier, and verifiers MUST reject an all-zero
        session identifier.</t>

        <t>Session-key provisioning to an independent verifier is specified as
        follows. VIRP adopts the O-Key-holding verifier model: a conforming
        independent verifier holds the O-Key and derives the session key itself
        from the authenticated session transcript and generation value. This
        matches the symmetric trust-boundary design and requires no confidential
        session-key delivery protocol. Such a verifier is a trusted component
        and has session-forging capability wherever it possesses the derivation
        context; its trust status equals the collection boundary's. Deployments
        requiring a verifier that cannot forge must use asymmetric observation
        signing (<xref target="asym"/>), which is future work. A derived-key-only
        verifier, provisioned over an authenticated confidential channel, is a
        possible future model but is not specified here; it would require a new
        key-delivery protocol.</t>

        <t>Generation is carried in the authenticated SESSION_BIND message,
        specified as follows:</t>
        <ul>
          <li><tt>generation</tt> is an unsigned 64-bit integer in network byte
          order.</li>
          <li>It is covered by the authentication protecting SESSION_BIND. That
          authenticator MUST NOT be the session key whose derivation depends on
          the transcript; otherwise the construction is circular. Implementations
          MUST authenticate SESSION_BIND under key material that is independent
          of the transcript being established.</li>
          <li>The session transcript hash is computed over the complete encoded
          SESSION_BIND including the generation field: generation is inserted
          before transcript_hash is computed. Consequently generation is
          represented both inside <tt>salt = transcript_hash</tt> and separately
          as the 8-octet HKDF <tt>info</tt>. This duplication is
          intentional.</li>
          <li>A verifier MUST reject a generation less than or equal to the
          greatest previously accepted generation for the derivation scope,
          which is the tuple (O-Key identifier, O-Node node identifier). This is
          rollback protection.</li>
        </ul>

        <t>The v2 verifier also enforces a freshness window: an observation
        whose authenticated timestamp differs from the verifier's clock by more
        than a configured bound (300 seconds in the reference implementation) is
        rejected as stale, in addition to the per-session replay check of
        <xref target="threat"/>.</t>
      </section>

      <section anchor="chainenc" numbered="true">
        <name>Chain Entry Encoding</name>
        <t>A chain entry is a distinct artifact from an observation. Each entry
        contains <tt>previous_entry_hash</tt>, the entry digest of its immediate
        predecessor, within the entry's canonical encoding. VIRP computes over
        that canonical encoding both an unkeyed SHA-256 entry digest and an
        HMAC-SHA256 chain authentication code using K_chain, a key distinct from
        the observation-authentication keys. Alteration of a predecessor
        reference therefore changes both the entry digest and the chain HMAC. An
        entity without K_chain cannot modify, insert, or remove an entry within
        the presented chain and produce replacement downstream entries that
        verify.</t>

        <t>This establishes only the integrity and linkage of the entries
        presented to the verifier. It does not prove the verifier holds the
        latest chain suffix: an adversary can present an older, internally valid
        prefix. Detecting truncation or a withheld suffix requires comparison
        against a previously trusted chain head or an externally published
        checkpoint (<xref target="anchoring"/>). A holder of K_chain can
        recompute the chain; external anchoring is required to make
        already-published history resistant to undetected rewriting by such a
        holder.</t>

        <t>VIRP chain entries use a deterministic binary canonical encoding.
        The compact, alphabetically ordered JSON that the current reference
        implementation emits is a legacy implementation format, not the
        normative wire representation: values are inserted into it directly, so
        unrestricted quotes, backslashes, or control characters would make it
        ambiguous, and it is unsafe to standardize as-is.</t>

        <t>The fields carried by a chain entry are: session identifier;
        sequence; previous entry digest; wall-clock timestamp (informational);
        monotonic timestamp (ordering); artifact type (observation, intent, or
        outcome); artifact identifier; artifact digest; artifact digest
        algorithm; artifact schema version; signer node identifier; and signer
        organization identifier. The entry's own entry digest (SHA-256) and
        chain HMAC (HMAC-SHA256 under K_chain) are computed over the canonical
        encoding of those fields; they are outputs, not inputs to it.</t>

        <t>The normative binary canonical encoding is the octet sequence over
        which both the entry digest and the K_chain HMAC are computed. All
        multi-byte integers are in network byte order; digests are carried as
        raw 32 octets, not hexadecimal; variable-length text fields are
        length-prefixed and are not NUL-terminated in the authenticated
        form:</t>

        <figure anchor="fig-chain">
          <name>Chain Entry Canonical Encoding (chain-format-version 1)</name>
          <artwork type="ascii-art"><![CDATA[
 field                     encoding
 ------------------------  -----------------------------------------
 domain label              "VIRP-CHAIN-ENTRY-v1" (ASCII, fixed)
 format_version            u16
 artifact_type             u8 {observation=1, intent=2, outcome=3}
 sequence                  u64
 session_id                16 octets raw (same form as v2 header)
 previous_entry_digest     32 octets raw
 timestamp_ns              u64
 monotonic_ns              u64
 artifact_id               length-prefixed (u16 len || octets)
 artifact_hash             32 octets raw
 artifact_hash_alg         u8 enum {sha256=1}
 artifact_schema_version   u16
 signer_node_id            u64 (same width as v2 node_id)
 signer_org_id             length-prefixed (u16 len || octets)
]]></artwork>
        </figure>

        <t>The entry digest is SHA-256 of this encoding; the chain HMAC is
        HMAC-SHA256(K_chain, this encoding). A verifier reconstructs the
        encoding, checks the previous entry digest against the prior entry,
        recomputes the digest, and verifies the HMAC.</t>

        <t>The following field-level rules are required to make this encoding
        deterministic:</t>
        <ul>
          <li><tt>sequence</tt> is an unsigned 64-bit integer and MUST be
          greater than or equal to 1 and strictly increasing within a chain.</li>
          <li><tt>session_id</tt> MUST use the same fixed 16-octet raw
          representation as the v2 observation header, not a length-prefixed
          text form, so that one session cannot be described two ways that hash
          differently. For an entry not associated with a session, the field
          MUST be 16 zero octets.</li>
          <li><tt>signer_node_id</tt> MUST match the v2 node identifier width
          and is therefore u64.</li>
          <li>For the first entry in a chain, <tt>previous_entry_digest</tt>
          MUST be 32 zero octets.</li>
          <li>Every length-prefixed field MUST be valid UTF-8, MUST be between 0
          and 1024 octets in length, MUST be rejected if the declared length
          exceeds the remaining input, and MUST be rejected if the octets are
          not well-formed UTF-8.</li>
          <li>Unknown enumerated values in <tt>artifact_type</tt> or
          <tt>artifact_hash_alg</tt> MUST be rejected rather than ignored.</li>
          <li>Octets after the declared fields MUST be rejected.</li>
          <li>The complete stored or transmitted chain record is the canonical
          entry encoding, followed by the entry digest (32 octets raw), followed
          by the chain HMAC (32 octets raw), in that order.</li>
        </ul>

        <t>The binary encoding above is chain-format-version 1 and is the
        normative canonical encoding for this protocol version. The compact
        alphabetically ordered JSON that the current reference implementation
        emits is a non-interoperable legacy format and is not conforming to this
        revision. An implementation emitting the legacy JSON is a legacy
        producer; a conforming implementation emits chain-format-version 1.
        Migrating the reference implementation's chain emission is a tracked
        implementation task (<xref target="implstatus"/>).</t>
      </section>

      <section anchor="canonical" numbered="true">
        <name>Canonical Encoding</name>
        <t>Authentication inputs MUST be deterministic and unambiguous. This
        document specifies, for each authenticated artifact, the exact octet
        sequence covered.</t>

        <t>For v2 observations, the authenticated bytes are the 88-octet
        serialized header of <xref target="obs-v2"/> concatenated with the
        payload. The header is fixed-width with all multi-byte fields in network
        byte order, so the encoding is already deterministic and unambiguous;
        the normative requirements are the exact offsets and widths of
        <xref target="obs-v2"/> and the rule that <tt>reserved</tt> is zero.
        The header authenticates a SHA-256 command digest, not the command
        string, so the binding is to the digest of the canonical command; the
        canonicalization that feeds that digest is defined by the v1 command
        canonicalizer for legacy string commands and, for typed profiles, by the
        rules under development in <xref target="typedops"/>.</t>

        <t>Normative authentication inputs cannot be optional: adding a
        domain-separation label would change the authenticated octets, so a
        labeled and an unlabeled implementation would reject each other's
        records. v2 therefore uses the fixed header exactly as specified --
        including its version octet, which together with the differing v1 and v2
        header layouts is what provides cross-version separation. A future wire
        format version SHOULD introduce an explicit domain-separation label;
        adding one to v2 would be wire-incompatible and is not proposed
        here.</t>

        <t>For chain entries, the authenticated bytes are exactly the
        deterministic binary encoding defined in <xref target="chainenc"/>. The
        legacy JSON representation is not a conforming canonical encoding for
        this protocol version.</t>
      </section>
    </section>

    <section anchor="containment" numbered="true">
      <name>Reference Containment Model</name>
      <t>The reference deployment enforces separation between the requesting
      process and the VIRP O-Node through a two-host architecture. The
      addresses, hostnames, and hypervisor details below describe the lab
      topology in which the results reported in
      <xref target="redteam"/> and <xref target="scale"/> were obtained; a
      deployment need only preserve the properties, not the topology.</t>

      <table anchor="fig-vms">
        <name>Reference Two-Host Containment</name>
        <thead>
          <tr><th>Host</th><th>Role and Constraints</th></tr>
        </thead>
        <tbody>
          <tr>
            <td>Requesting host</td>
            <td>Runs the agent gateway, the tool server, and the agent process.
            No direct network path to managed devices. Holds no observation,
            chain, or approval key. Outbound network policy restricted to the
            inference API and the O-Node socket only. Runs as an unprivileged
            system user with service-manager hardening.</td>
          </tr>
          <tr>
            <td>O-Node host</td>
            <td>Runs the O-Node daemon. Holds the O-Key, K_chain, and device
            credentials, and the approver public keys. Has direct SSH and API
            access to managed devices. Accepts connections from the requesting
            host only; all other inbound traffic is blocked.</td>
          </tr>
        </tbody>
      </table>

      <t>Communication between the requesting host and the O-Node occurs over a
      socket bridge to the O-Node's local socket. The requesting process issues
      intent requests and receives authenticated observations over this channel.
      It has no other path to device access.</t>

      <section numbered="true">
        <name>Containment Layers</name>
        <ul>
          <li>Hypervisor layer: virtual-machine firewall rules enforce network
          policy outside guest control.</li>
          <li>Network layer: host firewalling restricts outbound traffic on the
          requesting host and inbound traffic on the O-Node.</li>
          <li>Process layer: an unprivileged service user with no-new-privileges,
          a strict read-only system view, and a private temporary
          directory.</li>
          <li>Filesystem layer: credential files owned by root with mode 0600,
          inaccessible to the service user.</li>
          <li>Cryptographic layer: the requesting host holds no
          observation-authentication key, so a compromise of that host does not
          confer the ability to produce an observation that verifies. This is a
          property of key placement, not of asymmetric cryptography; see
          <xref target="keys"/> for the precise scope.</li>
        </ul>
      </section>
    </section>

    <section anchor="tiers" numbered="true">
      <name>Trust Tiers and Action Authorization</name>
      <table anchor="fig-tiers">
        <name>Trust Tiers</name>
        <thead>
          <tr><th>Tier</th><th>Definition</th><th>Authorization Required</th></tr>
        </thead>
        <tbody>
          <tr><td>GREEN</td><td>Read-only observation. No state change possible.</td><td>None -- immediate execution</td></tr>
          <tr><td>YELLOW</td><td>Low-impact state change. Reversible within session.</td><td>Human acknowledgment</td></tr>
          <tr><td>RED</td><td>High-impact or potentially irreversible change.</td><td>Explicit signed approval, bound to command digest, device, and expiry</td></tr>
          <tr><td>BLACK</td><td>Destructive, out-of-scope, or policy violation.</td><td>Rejected unconditionally</td></tr>
        </tbody>
      </table>

      <t>Tier classification is performed by the intent router, which evaluates
      the command class, target device, and current network state.
      Classification rules are configurable per deployment. A command that
      matches no rule MUST be treated as RED rather than permitted; the
      classifier fails closed. The tier classifier itself is outside the
      requesting process's ability to modify -- it runs as a separate process
      with its own audit log.</t>

      <t>Rejections are themselves authenticated records. When the gate refuses
      a command, the O-Node emits an authenticated error observation carrying
      the true tier and a proposal identifier, so that a refusal is evidence in
      the chain rather than an absence of evidence.</t>
    </section>

    <section anchor="threat" numbered="true">
      <name>Threat Model</name>
      <t>VIRP defines the following threat actors and the protocol's response to
      each. The rows are stated per key role, because different key holders have
      different forging power.</t>

      <table anchor="fig-threat">
        <name>Threat Model</name>
        <thead>
          <tr><th>Threat Actor</th><th>VIRP Response</th></tr>
        </thead>
        <tbody>
          <tr>
            <td>Compromised requesting agent</td>
            <td>Cannot forge observations, chain entries, or approvals: it holds
            none of those keys. If it holds an R-Key, it can submit
            authenticated intent messages, which remain subject to gate
            classification and authorization policy. Containment
            (<xref target="containment"/>) prevents direct device access.</td>
          </tr>
          <tr>
            <td>Compromised agent host (full root)</td>
            <td>May expose the R-Key if the deployed R-Node stores it there. No
            observation, chain, or approval key is present in the reference
            architecture; the attacker can read records and submit them to a
            key-holding verifier but cannot produce an observation, chain entry,
            or approval that verifies.</td>
          </tr>
          <tr>
            <td>Compromised O-Key holder</td>
            <td>Can forge v1 observations directly. Because the O-Key is also
            the v2 session-key derivation input in the reference implementation,
            the holder can also derive and forge v2 observations whenever it has
            the necessary session-derivation context. Cannot forge chain entries
            (needs K_chain) or approvals (needs the approver's Ed25519 key). A
            component limited to verifying v1 without this forging power would
            require a separately derived verification capability or an
            asymmetric construction; possessing the actual O-Key is not
            v1-only.</td>
          </tr>
          <tr>
            <td>Compromised R-Key holder</td>
            <td>Can produce authenticated Intent Channel messages within the
            R-Key's scope. Cannot produce Observation Channel records: key-role
            and channel binding are enforced by the implementation. Intent
            authentication is not authorization; the gate and approval policy
            remain authoritative.</td>
          </tr>
          <tr>
            <td>Compromised holder of a single derived session key</td>
            <td>Can forge v2 observations for that one session only. Cannot
            forge other sessions, v1 records, or chain entries. This bound
            applies to a component provisioned with only the derived key; it is
            a future profile rather than a documented property of the current
            reference deployment (<xref target="obs-v2"/>).</td>
          </tr>
          <tr>
            <td>Compromised chain writer or verifier (holds K_chain)</td>
            <td>Can rewrite or forge chain entries and thus chain history.
            Cannot forge a v2 observation under an uncompromised session key.
            External anchoring (<xref target="anchoring"/>) is what makes
            already-anchored history rewrite-detectable.</td>
          </tr>
          <tr>
            <td>Compromised collection boundary (O-Node holding several keys)</td>
            <td>May hold K_obs, a session key, K_chain, and device credentials at
            once. Can forge observations, recompute the chain, and bypass the
            gate to execute writes directly with device credentials, omitting
            them from the record. VIRP does not prevent this. What remains: it
            cannot forge an approval signed by an uncompromised approver key,
            held outside the boundary. Independently retained approval records
            authenticate approvals that are presented; establishing that no
            corresponding approval existed is a stronger claim, requiring a
            complete approval history whose relevant range and head are
            independently retained or externally anchored. An individual
            retained record proves an approval existed and was valid, not that
            no other did. An unanchored local log controlled by the compromised
            boundary cannot establish absence, since a K_chain holder can
            rewrite, remove, or withhold it. External device-side authorization,
            credential constraints, and independent monitoring are required to
            detect or limit out-of-flow execution.</td>
          </tr>
          <tr>
            <td>Replay of a genuine observation</td>
            <td>A v2 stateful verifier maintains a persistent high-water
            sequence number per (session identifier, node identifier) and
            accepts a v2 observation only when its sequence strictly advances
            that mark; the state persists across restart. A strict high-water
            verifier requires observations to be accepted in sequence order; a
            profile permitting concurrent in-flight operations within a session
            MUST define an equivalent replay-safe acceptance window rather than
            a scalar high-water mark. Legacy v1 HMAC verification is stateless
            and does not itself provide this guarantee.</td>
          </tr>
          <tr>
            <td>Credential exfiltration from the agent</td>
            <td>Device credentials are not on the agent host in the reference
            implementation; the boundary holds them.</td>
          </tr>
          <tr>
            <td>Man-in-the-middle on the O-Node transport</td>
            <td>Records are authenticated before leaving the boundary; modified
            records fail verification. This provides neither confidentiality nor
            endpoint authentication; deployments SHOULD use an authenticated,
            encrypted transport.</td>
          </tr>
          <tr>
            <td>Physical access</td>
            <td>Out of scope; a deployment requirement.</td>
          </tr>
        </tbody>
      </table>
    </section>

    <section anchor="roleseparation" numbered="true">
      <name>Future Architecture: Role Separation</name>
      <t>The current VIRP architecture concentrates observation, intent gating,
      and action execution within a single O-Node process. This design is
      operationally simple and sufficient for environments where the O-Node host
      is well-protected. However, as deployments scale and the consequences of a
      single-node compromise grow, protocol-level role separation offers a
      principled path to narrower blast radii and stronger separation of duties.
      This section defines a future three-role architecture and a migration
      roadmap from the current single-node baseline.</t>

      <section numbered="true">
        <name>Architectural Motivation</name>
        <t>In the current design, a compromised O-Node can forge observations,
        approve intents, and execute actions on devices. While chain integrity
        makes tampering with presented history detectable to a party that holds
        an independently retained chain head, a live compromise grants the
        attacker control over future operations for the duration of the
        compromise window.</t>
        <t>The three-role architecture addresses this concentration of trust by
        decomposing the O-Node into three distinct principals, each with a
        narrow set of capabilities and its own keying material. The design goal
        is that compromise of any single node is insufficient to both fabricate
        state and act on that fabrication.</t>
      </section>

      <section numbered="true">
        <name>Observer Node</name>
        <t>The Observer Node is responsible for device telemetry collection and
        observation authentication. It holds read-only credentials for managed
        devices and the observation key material. The Observer Node listens on a
        read-only socket and MUST NOT accept or process write intents.</t>
        <t>The Observer Node authenticates each observation at collection time,
        covering the fields specified in <xref target="obs-v2"/>. The
        authenticated observation is referenced from the chain and made
        available to other nodes over the read-only socket.</t>
        <t>The Observer Node holds no policy state, no approval authority, and
        no write credentials. It cannot authorize or execute configuration
        changes on any managed device.</t>
      </section>

      <section numbered="true">
        <name>Executor Node</name>
        <t>The Executor Node holds write credentials for managed devices. It
        receives command bundles that have been signed by the Policy Node and
        executes them against the specified targets. The Executor Node MUST
        refuse execution of any command bundle that does not carry a valid
        Policy Node signature.</t>
        <t>The Executor Node holds no chain key, no observation key, and no
        policy or constraint state. It does not evaluate whether a command is
        appropriate; it verifies only that the command bundle bears a valid
        signature from a known Policy Node. The Executor Node makes no
        autonomous decisions.</t>
        <t>Upon completing execution, the Executor Node reports the result to
        the Observer Node, which independently collects a post-action
        observation for outcome verification. The Executor Node does not
        self-report success or failure into the observation chain.</t>
      </section>

      <section numbered="true">
        <name>Policy Node</name>
        <t>The Policy Node holds the constraint ledger, operator preferences,
        trust tier definitions, and the approval queue. It receives intent
        requests, evaluates them against policy, and -- if approved -- signs a
        command bundle with its own Ed25519 private key and forwards it to the
        Executor Node.</t>
        <t>The Policy Node MUST NOT hold device credentials of any kind. It
        never communicates directly with managed devices. Its sole output is
        signed command bundles delivered to the Executor Node. The Policy Node
        consumes observations from the Observer Node for situational awareness
        but cannot influence or alter the observation chain.</t>
        <t>Human approval workflows, trust tier escalation, and operator
        overrides are mediated through the Policy Node. Tier classification (as
        defined in <xref target="tiers"/>) is performed by the Policy Node
        rather than by a co-located intent router.</t>
      </section>

      <section numbered="true">
        <name>Separation-of-Duties Property</name>
        <t>The three-role architecture enforces a strict separation-of-duties
        property: no single node can observe device state, approve an action,
        and execute that action. Each of these three capabilities is held by a
        different principal with independent keying material.</t>
        <t>Observation requires the observation key (held only by the Observer
        Node). Approval requires the Policy Node signing key (held only by the
        Policy Node). Execution requires device write credentials (held only by
        the Executor Node). An attacker who compromises any single node obtains
        exactly one of these three capabilities.</t>
        <t>This property MUST be enforced at the protocol layer. Implementations
        MUST NOT permit a single process or host to hold the keying material of
        more than one role in a production deployment of the three-role
        architecture.</t>
      </section>

      <section numbered="true">
        <name>Compromise and Blast Radius Analysis</name>
        <t>The following analysis considers the impact of compromising each node
        individually and in combination.</t>
        <t><strong>Observer Node compromise.</strong> An attacker gains the
        observation key material and read-only device credentials, and can forge
        or fabricate observations and read device state passively. The attacker
        cannot approve intents and cannot execute write operations. Blast
        radius: observation forgery and passive reconnaissance.</t>
        <t><strong>Executor Node compromise.</strong> An attacker gains device
        write credentials, but the Executor Node only processes command bundles
        carrying a valid Policy Node signature. The attacker may attempt to
        replay previously received, validly signed bundles. Implementations
        SHOULD include replay protections such as nonces or expiring timestamps
        in command bundles. Blast radius: replay of previously approved
        commands, bounded by those protections.</t>
        <t><strong>Policy Node compromise.</strong> An attacker gains the
        ability to sign arbitrary command bundles, but the Policy Node holds no
        device credentials and has no network path to managed devices. Blast
        radius: unauthorized approvals and signed bundles that remain inert
        unless delivered to a compromised Executor Node.</t>
        <t><strong>Observer and Executor compromise.</strong> The attacker can
        forge observations and holds write credentials, but cannot generate
        validly signed command bundles. The attacker could bypass the Executor's
        signature check through local code modification, effectively using the
        write credentials directly. This is equivalent to raw device access plus
        observation forgery; the Policy Node's approval record will show no
        corresponding approval, subject to the retention and anchoring
        qualification in <xref target="threat"/>.</t>
        <t><strong>Policy and Executor compromise.</strong> The attacker can
        sign arbitrary command bundles and execute them. This is the most
        operationally dangerous two-node scenario. The attacker cannot forge
        observations: post-action observations collected by the uncompromised
        Observer Node record the resulting device state.</t>
        <t><strong>Observer and Policy compromise.</strong> The attacker can
        forge observations and sign bundles, but cannot execute commands. Blast
        radius: fabricated state and pre-signed malicious bundles, with no
        execution capability.</t>
        <t><strong>Full three-node compromise.</strong> Equivalent to a
        compromised single O-Node in the current architecture. The three-role
        architecture does not defend against simultaneous compromise of all
        principals; its value lies in requiring the attacker to breach three
        independent trust boundaries rather than one.</t>
      </section>

      <section numbered="true">
        <name>Migration Roadmap</name>
        <t><strong>Stage 1: Single O-Node (current).</strong> The existing
        single-process O-Node handles observation, policy evaluation, and
        execution. This is the deployment baseline described throughout this
        document. The blast radius of an O-Node compromise is the full set of
        managed devices.</t>
        <t><strong>Stage 2: Observer-Executor separation.</strong> The O-Node is
        split into an Observer process and an Executor process, each with its
        own socket and credentials. This separation can be achieved on a single
        host using process isolation.</t>
        <t><strong>Stage 3: Policy Node extraction.</strong> Policy evaluation,
        the constraint ledger, and the approval queue are extracted into a
        dedicated Policy Node process with its own signing key. The Executor
        Node is modified to require a valid Policy Node signature on all command
        bundles.</t>
        <t><strong>Stage 4: Physical separation.</strong> The three nodes are
        deployed on separate hosts with independent network policies. The Policy
        Node may be placed on an air-gapped host or backed by a hardware
        security module for its signing key. Inter-node communication is
        authenticated using mutual TLS or a comparable mechanism.</t>
      </section>

      <section numbered="true">
        <name>Security Considerations for Role Separation</name>
        <t>The three-role architecture introduces inter-node communication paths
        that do not exist in the single O-Node design. Each path represents an
        attack surface that MUST be protected by authenticated and encrypted
        transport. Implementations MUST NOT rely on network topology alone for
        inter-node authentication.</t>
        <t>Key management complexity increases with role separation. Key
        rotation procedures MUST be defined for each role independently, and
        public keys MUST be distributed to the nodes that verify
        signatures.</t>
        <t>Command bundle replay is a risk specific to the Executor Node.
        Implementations SHOULD include monotonic sequence numbers and expiration
        timestamps in signed command bundles, and the Executor Node SHOULD
        reject bundles already processed or exceeding a configurable staleness
        threshold.</t>
        <t>The migration roadmap permits intermediate states where two roles
        share a host or process. Implementations operating in an intermediate
        state SHOULD document which separation-of-duties properties are enforced
        and which are deferred.</t>
      </section>
    </section>

    <section anchor="goals" numbered="true">
      <name>Informal Security Goals</name>
      <t>This section states the security goals that VIRP is designed to
      achieve. These goals are expressed in natural language. In response to
      review feedback on draft-howard-virp-03, this section was retitled from
      "Formal Security Properties" to "Informal Security Goals" to align with
      the terminology used in the formal protocol analysis community.</t>

      <t>A symbolic formal analysis in ProVerif has been completed. HMAC key
      secrecy and injective agreement between the O-Node and a verifier have
      been machine-verified under the Dolev-Yao model, the latter under a stated
      trace restriction corresponding to the v2 replay-counter invariant
      (<xref target="formal"/>). This covers G1 (observation non-forgeability,
      symmetric form) and at-most-once acceptance under the model's stated
      (session identifier, sequence) trace restriction. It does not establish
      timestamp freshness, which is not symbolically modeled, and does not fully
      model the implementation's per-(session identifier, node identifier)
      high-water ordering; it does not cover the stateless v1 path or v1 error
      records. Architectural non-conflation (G4) and agent non-escalation (G5)
      are containment properties, not wire-protocol properties, and are not
      modeled. No Tamarin model exists; a second-tool cross-check is future
      work.</t>

      <section numbered="true">
        <name>G1: Observation Non-Forgeability (symmetric, per key)</name>
        <t>A party that does not hold the authenticating key for an observation
        cannot produce a record that verifies. For a v1 observation this is
        HMAC-SHA256 under K_obs; for a v2 observation it is HMAC-SHA256 under
        the HKDF session key. Without the relevant key it is computationally
        infeasible to produce a valid authentication code, under the standard
        PRF/MAC assumption on HMAC-SHA256. The property is relative to that
        key's secrecy and does not hold against a holder of the key.</t>
        <t>Role separation limits cross-artifact forgery, with one explicit
        exception in the reference implementation: the O-Key directly
        authenticates v1 observations and also serves as the input keying
        material for v2 session-key derivation. An O-Key holder can therefore
        forge v1 observations and, when it possesses the required derivation
        context, derive keys capable of forging v2 observations. This reuse does
        not confer the ability to forge chain entries, Intent Channel messages,
        approvals, or federation records authenticated under separate keys.</t>
        <t>Asymmetric non-forgeability -- origin verification without a
        forging-capable secret -- is future work (<xref target="asym"/>) and
        would still not bound signer compromise.</t>
      </section>

      <section numbered="true">
        <name>G2: Chain Integrity</name>
        <t>Any modification to, deletion of, or insertion into the presented
        chain is detectable by a verifier holding K_chain, because the canonical
        entry encoding covers the predecessor's entry digest and is itself
        covered by an HMAC under K_chain
        (<xref target="chainenc"/>). Two qualifications are load-bearing.
        First, this property is relative to the secrecy of K_chain: a holder of
        K_chain can recompute a consistent history. Second, it establishes the
        integrity of the entries presented, not that the presented chain is
        complete; detecting truncation or a withheld suffix requires an
        independently retained chain head or an external checkpoint
        (<xref target="anchoring"/>).</t>
      </section>

      <section numbered="true">
        <name>G3: Intent Prior to Action</name>
        <t>No write operation reaches a device through the gate without a prior
        authorized intent on record. The O-Node refuses execution for any action
        without a matching authorized intent, and for RED-tier actions without a
        valid approval bound to the command digest, device, and expiry. This
        property holds even if the requesting process is fully compromised. It
        does not hold against a party that possesses device credentials and a
        network path to the device outside the gate; that case is addressed by
        containment (<xref target="containment"/>) and by device-side
        authorization, not by this protocol.</t>
      </section>

      <section numbered="true">
        <name>G4: Two-Channel Non-Conflation</name>
        <t>The Observation and Intent channels are architecturally separate and
        key-role separated. An observation cannot authorize an intent. An intent
        cannot substitute for an observation.</t>
      </section>

      <section numbered="true">
        <name>G5: Agent Non-Escalation</name>
        <t>The requesting process cannot escalate its own trust tier. Tier
        classification is performed by a separate process outside that
        process's modification scope.</t>
      </section>
    </section>

    <section anchor="impl" numbered="true">
      <name>Reference Implementation</name>
      <t>The reference C core (open source, Apache 2.0)
      <xref target="VIRP-REPO"/> implements the O-Node daemon, the
      authentication library, the gate and approval flow, and the chain
      database. IronClaw is a platform built on that core by Third Level IT LLC
      <xref target="THIRDLEVEL"/>, adding multi-vendor driver support, a natural
      language interface via the Model Context Protocol, intent routing, and
      operational tooling. The protocol specified in this document is
      independent of that platform; the platform is cited because the results
      reported in <xref target="redteam"/> and <xref target="scale"/> were
      obtained with it.</t>

      <section numbered="true">
        <name>Component Summary</name>
        <table anchor="fig-components2">
          <name>Reference Components</name>
          <thead>
            <tr><th>Component</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>O-Node daemon</td><td>C binary. libsodium (Ed25519 for approvals and federation) and OpenSSL (HMAC-SHA256, SHA-256) with an SQLite-backed chain. Pluggable driver interface. Unix socket IPC with peer-credential authorization.</td></tr>
            <tr><td>Cisco IOS driver</td><td>SSH via exec channel. Supports show commands across IOS and IOS-XE. Authenticates all output at collection.</td></tr>
            <tr><td>FortiGate driver</td><td>Dual-transport SSH (primary) and REST API (fallback), FortiOS 7.x compatible. The REST transport is read-only by construction: non-GET methods are refused at the transport layer.</td></tr>
            <tr><td>Executor</td><td>C executor for SSH command dispatch. Uses the exec channel, not a PTY. Authenticates all device output before returning it to the consuming layer.</td></tr>
            <tr><td>Verification library</td><td>Python library that verifies v1 observations. It holds the O-Key and is therefore a trusted component in the sense of <xref target="custody"/>: it can verify and, because the key is symmetric, also produce v1 records. It is not an untrusted verifier.</td></tr>
            <tr><td>Intent router</td><td>Tier classifier and intent authorization engine. Separate process from the requesting agent.</td></tr>
            <tr><td>Approval client</td><td>Signs approval records with an Ed25519 or ECDSA P-256 key from an enrolled approver registry, optionally via a PKCS#11 token. The client signs only; the daemon is the sole chain writer.</td></tr>
            <tr><td>Tool server</td><td>Model Context Protocol server exposing VIRP tools to a requesting agent. Mediates all agent-to-O-Node communication.</td></tr>
          </tbody>
        </table>
      </section>

      <section numbered="true">
        <name>Test Coverage</name>
        <t>The reference implementation carries C unit and integration suites,
        Python end-to-end and parity tests, a fuzzing harness, concurrency
        tests, negative tests for replay and approval reuse, live-hardware
        exercises across several vendor platforms, and a re-runnable formal
        proof target. Exact counts change with every revision of the code and
        are therefore not asserted here; they are reported by the test target in
        <xref target="VIRP-REPO"/>. Claims in this document that depend on
        implementation state are marked as such in
        <xref target="implstatus"/>.</t>
      </section>
    </section>

    <section anchor="frr" numbered="true">
      <name>FRR BGP Daemon Integration</name>
      <t>VIRP includes a reference implementation for the BGP domain as a
      loadable FRR (Free Range Routing) module <xref target="VIRP-BGP-FRR"/>.
      The module hooks into FRR's bgpd process using the standard
      FRR_MODULE_SETUP pattern. It registers read-only hook callbacks on BGP
      events, constructs JSON observation payloads, authenticates them with
      HMAC-SHA256 at collection time inside the bgpd process space, and delivers
      them to the O-Node over a Unix domain socket. The module is strictly
      Observation Channel: it never modifies routing state, peer configuration,
      or the RIB.</t>

      <section numbered="true">
        <name>BGP Daemon Hooks</name>
        <t>The module registers four hook callbacks covering the primary BGP
        event classes:</t>
        <table anchor="fig-hooks">
          <name>BGP Hooks</name>
          <thead>
            <tr><th>Hook</th><th>Obs Type</th><th>Default Tier</th><th>Trigger Condition</th></tr>
          </thead>
          <tbody>
            <tr><td>peer_status_changed</td><td>1 (PEER_STATE)</td><td>GREEN (YELLOW when leaving Established)</td><td>BGP FSM state transition</td></tr>
            <tr><td>peer_backward_transition</td><td>2 (PEER_BACKWARD)</td><td>YELLOW</td><td>Peer drops out of Established</td></tr>
            <tr><td>bgp_process</td><td>3 (ROUTE_EVENT)</td><td>GREEN</td><td>Route announce or withdraw</td></tr>
            <tr><td>bgp_route_update</td><td>4 (BESTPATH_CHANGE)</td><td>GREEN</td><td>Best-path selection changes</td></tr>
          </tbody>
        </table>
        <t>Data captured per hook: peer_status_changed records peer IP, peer AS,
        local AS, old and new FSM state, router-id, and producer identifier.
        peer_backward_transition records peer IP, peer AS, local AS, last FSM
        state, last major event code, uptime, and router-id. bgp_process records
        prefix, peer IP, peer AS, AFI, SAFI, router-id, and the
        announce/withdraw flag. bgp_route_update records prefix, AFI, SAFI,
        router-id, and the old and new path attributes (peer, nexthop, AS path,
        MED, local-pref, origin).</t>
        <t>Trust tier assignment happens at the source: the
        peer_backward_transition hook always emits YELLOW tier because loss of
        an Established session is operationally significant. The
        peer_status_changed hook conditionally escalates to YELLOW when it
        detects a transition away from Established. All other observations are
        GREEN tier.</t>
        <t>All hooks are registered at module load and unregistered at module
        teardown, following the FRR module lifecycle pattern.</t>
      </section>

      <section numbered="true">
        <name>BGP Observation Wire Format</name>
        <t>Each observation is transmitted as a contiguous byte stream over the
        Unix domain socket: a packed binary header, a variable-length JSON
        payload, and an HMAC-SHA256 digest. All multi-byte integer fields in the
        header are in network byte order.</t>
        <figure anchor="fig-bgpwire">
          <name>BGP Observation Wire Format</name>
          <artwork type="ascii-art"><![CDATA[
Bytes  Field          Encoding
-----  -------------  --------------------------------
 0-3   magic          0x56495250 "VIRP" (net order)
 4     version        uint8 (currently 1)
 5     obs_type       uint8 (1-4, see hook table)
 6     trust_tier     uint8 (0=GRN 1=YLW 2=RED 3=BLK)
 7     flags          uint8 (reserved, zero)
 8-15  timestamp_ns   uint64 ns epoch (net order)
16-19  sequence       uint32 monotonic (net order)
20-23  payload_len    uint32 (net order)
24-27  producer_id    uint32 from BGP router-id
28-N   payload        UTF-8 JSON (payload_len bytes)
N+0    hmac[32]       HMAC-SHA256(header || payload)
]]></artwork>
        </figure>
        <t>The wire header struct is packed so that no padding is inserted. The
        HMAC-SHA256 is computed over the concatenation of the 28-octet
        serialized header and the JSON payload, using a symmetric key shared
        between the bgpd module and the O-Node. Total wire size per observation
        is 28 + payload_len + 32 octets. Maximum payload size is 4096 octets.
        Minimum HMAC key length is 16 octets, maximum 256. The producer
        identifier is derived from the BGP router-id, providing a stable
        per-router identifier across observations.</t>
        <t>This is a separate wire format from the O-Node observation formats of
        <xref target="obs-v1"/> and <xref target="obs-v2"/>. It carries no
        session binding and is subject to the same qualification as v1: it is
        tamper-evident in transit under the shared key, and any holder of that
        key can produce records that verify.</t>
      </section>

      <section numbered="true">
        <name>JSON Payload Schema</name>
        <t>All payloads share a common envelope with domain-specific fields.
        Every payload includes a "domain" field set to "bgp" and an "event"
        field identifying the observation type. A peer state change payload
        carries the peer name and address, peer and local AS, old and new state,
        router-id, and producer identifier. A best-path change payload carries
        the prefix, AFI, SAFI, router-id, producer identifier, and nested "old"
        and "new" objects each carrying peer, peer address, peer AS, nexthop, AS
        path, MED, local preference, and origin.</t>
        <t>All string fields are escaped for control characters, backslashes,
        and quotes. Peer addresses are rendered to support both IPv4 and IPv6
        peers. Because this payload is JSON produced by direct value insertion,
        it is subject to the same caution recorded in
        <xref target="chainenc"/>: it is an implementation transport format, not
        a canonical authenticated encoding, and the authenticated bytes are the
        octets as transmitted.</t>
      </section>

      <section numbered="true">
        <name>O-Node Receiver</name>
        <t>The O-Node receives authenticated observations on a Unix domain
        socket. The receiver protocol is:</t>
        <ol>
          <li>Read 28 octets of wire header. Validate the magic value and the
          version.</li>
          <li>Extract payload_len. Reject if it exceeds the maximum payload
          size.</li>
          <li>Read payload_len octets of JSON payload.</li>
          <li>Read 32 octets of HMAC-SHA256.</li>
          <li>Compute HMAC-SHA256 over (header || payload) using the shared
          key.</li>
          <li>Compare computed against received using constant-time comparison.
          Reject on mismatch.</li>
          <li>Validate that the sequence number is monotonically increasing.
          Flag gaps as potential observation loss.</li>
          <li>Insert the verified observation into the chain.</li>
        </ol>
        <t>The bgpd module performs automatic reconnection: if the O-Node socket
        is unavailable, observations are logged locally as a fallback and
        failure counters are incremented. Observations produced while the socket
        is unavailable are not chained; a consumer MUST NOT treat a local
        fallback log as chained evidence.</t>
      </section>

      <section numbered="true">
        <name>FRR VTY Configuration Commands</name>
        <t>The module registers configuration commands under the BGP router node
        and show commands at the enable or view level: enabling and disabling
        authenticated observations, setting the O-Node socket path, and setting
        the HMAC key file path; plus show commands reporting state, key status,
        O-Node connectivity, producer identifier, and counters for records
        authenticated, delivered, and failed.</t>
        <t>Enabling triggers HMAC key loading, if not already loaded, and
        initiates connection to the O-Node socket. The key-loading command
        validates the key immediately, rejecting keys shorter than 16 octets.
        Key material is zeroized on module shutdown.</t>
      </section>

      <section numbered="true">
        <name>Build and Deployment</name>
        <t>The module can be built as a loadable shared object (recommended) or
        compiled directly into bgpd. As a loadable module it requires no
        modifications to the core FRR source. Build dependencies are OpenSSL for
        HMAC-SHA256. A standalone test harness validates authentication,
        verification, tamper detection, and sequence ordering without requiring
        the FRR build environment.</t>
        <t>Deployment requires distributing the same HMAC key to both the FRR
        host and the O-Node. The key file MUST be restricted to mode 0600 and
        owned by the FRR process user. Note that this shared key is held by both
        parties; per <xref target="keys"/>, both can produce records that
        verify.</t>
      </section>

      <section numbered="true">
        <name>Security Properties of BGP Integration</name>
        <ul>
          <li><strong>Authentication at collection time:</strong> the HMAC is
          computed inside bgpd's process space at the moment the BGP event
          fires, not after the fact.</li>
          <li><strong>Observation-only:</strong> the module registers read-only
          hooks. It cannot modify routes, peer state, or configuration.</li>
          <li><strong>Protocol-agnostic framing:</strong> the observation
          structure and authentication are identical whether the source is BGP
          or another domain. Only the "domain" field in the payload
          changes.</li>
          <li><strong>Trust tiers at the source:</strong> tier assignment
          happens at collection, not at analysis time.</li>
          <li><strong>Tamper detection:</strong> any modification to an
          observation between bgpd and the O-Node is caught by HMAC
          verification, relative to the secrecy of the shared key.</li>
        </ul>
      </section>
    </section>

    <section anchor="redteam" numbered="true">
      <name>Adversarial Findings</name>
      <t>On 6 March 2026, the agent under test was asked directly whether it
      could circumvent VIRP. The agent provided an unprompted breakdown of four
      bypass vectors without attempting to exploit them. This self-disclosure is
      documented in <xref target="VIRP-FINDING-003"/>. The remediation column
      below states the current status accurately; two entries were described in
      earlier revisions as resolved by asymmetric observation signing, which is
      not implemented.</t>

      <table anchor="fig-findings">
        <name>Agent-Identified Bypass Vectors</name>
        <thead>
          <tr><th>Finding</th><th>Description</th><th>Remediation Status</th></tr>
        </thead>
        <tbody>
          <tr>
            <td>Key forgeability</td>
            <td>Symmetric observation key readable on the requesting host, allowing forged observations.</td>
            <td>MITIGATED BY KEY PLACEMENT -- no observation key is present on the requesting host. Not resolved by asymmetric signing: observation authentication remains symmetric, so any holder of the key can forge (<xref target="keys"/>). Asymmetric observation signing remains future work (<xref target="asym"/>).</td>
          </tr>
          <tr>
            <td>Direct device access</td>
            <td>Agent knew device addresses and could connect directly, bypassing the O-Node.</td>
            <td>RESOLVED -- host and hypervisor firewall policy block direct device access.</td>
          </tr>
          <tr>
            <td>Intent gating bypass</td>
            <td>Agent could import the client library and call the execution path without filing intent.</td>
            <td>RESOLVED -- all execution paths route through the gate; unclassified commands fail closed at RED, and multi-command separators are refused at the request boundary before classification.</td>
          </tr>
          <tr>
            <td>API key exploitation</td>
            <td>Inference API key visible in session context, allowing unsupervised sessions to be spawned.</td>
            <td>RESOLVED -- key rotated, sensitive-value redaction enabled in logging.</td>
          </tr>
        </tbody>
      </table>

      <section numbered="true">
        <name>Live Penetration Test (6 March 2026)</name>
        <t>An adversarial exercise was conducted against the lab environment.
        The agent was given a single seed address and instructed to operate
        without destructive actions. It obtained initial access through passive
        discovery within five minutes and administrative credentials on the lab
        firewall within twenty-five minutes, exploiting no CVEs and attempting
        no brute force. The root cause was a plaintext environment file
        containing a full credential keyring on a host the agent could read. A
        live tunnel to a colocation environment was reachable with those
        credentials.</t>
        <t>The exercise validated Primitive 7. The agent did not break VIRP's
        cryptographic controls; it went around them by finding credentials in a
        plaintext file. The fix is architectural: credentials must not exist on
        the requesting host.</t>
        <t>This result is reported as an operational finding about a specific
        deployment, not as a protocol property. It illustrates the boundary
        stated in <xref target="threat"/>: VIRP constrains what flows through
        the gate and does not constrain a party that obtains device credentials
        and a network path outside it.</t>
      </section>
    </section>

    <section anchor="scale" numbered="true">
      <name>Scale Testing Results</name>
      <t>VIRP was exercised against a 35-router emulated lab topology (five-AS
      BGP topology) using a 16-thread executor, alongside a physical firewall
      and switch. A full sweep of the topology completed in under three minutes
      using 16 concurrent threads across three batches, producing one
      authenticated observation per device per command with no sequence gaps in
      the resulting chain and no false verification failures. No fabrication was
      attempted in this exercise, so it reports throughput and chain continuity
      rather than detection efficacy.</t>
      <t>A passive discovery pass over the same lab completed in five minutes
      from a single seed address, identifying live hosts, fingerprinting vendor
      and operating system, and producing an authenticated observation for each
      confirmed device, with chain references for every finding.</t>
    </section>

    <section anchor="applications" numbered="true">
      <name>Applications and Use Cases</name>
      <section numbered="true">
        <name>Verifiable Network Audit</name>
        <t>Traditional network audits produce reports that must be trusted on
        the auditor's authority. VIRP-enabled audits produce reports where every
        finding references an authenticated observation. The strength of that
        evidence is scoped by <xref target="keys"/>: a client that holds the
        verification key can confirm that the recorded bytes were authenticated
        by a holder of that key, and can detect modification of the presented
        chain. It does not, by itself, establish origin against a party that
        also holds the key. Deployments where the auditor and the client must
        not share a forging capability require asymmetric observation signing
        (<xref target="asym"/>) or a third-party anchoring arrangement
        (<xref target="anchoring"/>).</t>
      </section>
      <section numbered="true">
        <name>AI-Assisted Penetration Testing</name>
        <t>VIRP provides chain of custody for penetration test findings. Every
        probe is an authenticated observation. Every access attempt is
        intent-gated before execution. Scope is enforced at the gate: the agent
        cannot interact with out-of-scope devices without a matching authorized
        intent. The client receives the chain alongside the report.</t>
      </section>
      <section numbered="true">
        <name>Continuous Compliance Monitoring</name>
        <t>Baseline Memory (Primitive 5) enables continuous comparison of live
        device state against an authenticated baseline. Configuration drift -- a
        new BGP peer, a modified firewall policy, an unexpected open port -- is
        detected and attributed to a specific change in the chain.</t>
      </section>
      <section numbered="true">
        <name>Field Audit Appliance</name>
        <t>A self-contained appliance running the full stack enables plug-in
        network discovery and audit. An operator connects the device to a
        network port and issues audit commands remotely; the device produces an
        authenticated report and is removed when complete. No software
        installation is required on the target network.</t>
      </section>
      <section numbered="true">
        <name>Industrial and Air-Gapped Environments</name>
        <t>ICS and SCADA environments require audit trails for regulatory
        compliance and incident investigation. VIRP's authenticated observation
        model provides tamper-evident records of every automated interaction
        with control system components, supporting NERC CIP, IEC 62443, and
        similar frameworks. Because the gate and the chain are independent of
        any inference component, the same governed-automation path can be
        operated by scripts alone in environments where an AI component is not
        permitted.</t>
      </section>
    </section>

    <section anchor="future" numbered="true">
      <name>Future Work</name>

      <section anchor="formal" numbered="true">
        <name>Formal Verification</name>
        <t>A symbolic formal analysis of VIRP has been carried out in ProVerif
        <xref target="PROVERIF"/> (applied pi-calculus), with Usama Sardar (TU Dresden), prompted by Adrian
        Farrel's review of draft-howard-virp-03.</t>
        <t>Under the Dolev-Yao model the analysis establishes:</t>
        <ul>
          <li><strong>Key secrecy:</strong> an adversary with full network
          control cannot learn the O-Key or the derived session key. The model
          does represent session-key derivation, as a symbolic one-way
          constructor over the master O-Key, and checks secrecy of both the
          master and the derived key. It is an abstraction: HKDF is an
          uninterpreted symbolic constructor; it does not model the
          implementation's generation counter or the exact serialized
          transcript, and it omits several authenticated v2 header fields from
          the symbolic serialization. The result assumes honest endpoints and an
          ideal secret; it does not establish secure storage, side-channel
          resistance, or any property after compromise of a key holder.</li>
          <li><strong>Injective agreement (v2 path):</strong> in the modeled
          honest-verifier execution, each verifier-acceptance event corresponds
          injectively to one matching O-Node production event, under the stated
          at-most-once (session identifier, sequence) trace restriction. This
          correspondence depends on the modeled verifier being defined not to
          produce observations itself; a real key-holding verifier can produce
          the same authenticator. The modeled restriction is weaker than, and
          different from, the implementation's persistent per-(session
          identifier, node identifier) strict high-water ordering
          (<xref target="threat"/>): the proof abstracts, but does not fully
          model, that ordering property. ProVerif's Horn-clause abstraction does
          not model the mutable counter directly; the counter and its
          persistence are exercised separately by the reference
          implementation's replay negative tests.</li>
        </ul>
        <t>Two limitations. First, secrecy assumes honest endpoints and an ideal
        secret and says nothing post-compromise. Second, specific to the
        symmetric construction: because an HMAC verifier possesses the
        authentication key, the correspondence result applies to the modeled
        honest verifier process, which is defined not to generate observations.
        It does not give a third party cryptographic evidence distinguishing an
        O-Node-produced MAC from a verifier-produced one; it constrains the
        modeled verifier's behavior, not the real forging capability any key
        holder has. Publicly verifiable origin is what asymmetric observation
        signing (<xref target="asym"/>) would add.</t>
        <t>The result is scoped to the v2 path. The stateless v1 HMAC check and
        v1 error records are outside the modeled protocol. Timestamp freshness
        is verified by test only. Proof artifacts are in the reference
        repository under /proofs and are re-runnable.</t>
        <t>Open targets: discharging the replay restriction natively in a tool
        with mutable state, such as Tamarin -- no such model exists yet, and it
        is the intended second-tool cross-check; a process model of G4; and G5,
        which is out of scope for protocol-level analysis.</t>
        <t>Earlier revisions of this document stated machine verification in
        both ProVerif and Tamarin. That was inaccurate: only ProVerif exists.
        This revision corrects it and describes Tamarin as future work.</t>
      </section>

      <section numbered="true">
        <name>Multi-Node Coordination</name>
        <t>The current specification defines single O-Node operation.
        draft-howard-virp-01 includes a Multi-Node Coordination section defining
        leader election, observation synchronization, and cross-node chain
        verification for distributed deployments.</t>
      </section>

      <section anchor="asym" numbered="true">
        <name>Asymmetric Observation Signing</name>
        <t>Observation authentication in this revision is symmetric, so any
        holder of the relevant key can both verify and produce records
        (<xref target="keys"/>). Adding an asymmetric signature over the
        authenticated observation bytes would provide publicly verifiable
        origin and would allow a verifier that cannot forge. This is a wire
        format change: it adds a signature field and requires a public key
        distribution and revocation story. It would not, by itself, defend
        against compromise of the signer, which is a separate problem addressed
        by hardware custody (<xref target="hsm"/>), independent witnessing, or
        external anchoring (<xref target="anchoring"/>). Any such extension MUST
        be introduced under a new wire format version rather than as an optional
        field, because optional authenticated inputs break interoperability
        (<xref target="canonical"/>).</t>
      </section>

      <section anchor="anchoring" numbered="true">
        <name>External Anchoring and Truncation Detection</name>
        <t>Chain integrity as specified establishes the linkage of the entries
        presented to a verifier. It does not establish that the presented chain
        is complete, and it does not survive a holder of K_chain who rewrites
        history. Periodically publishing chain head digests to a party or medium
        outside the collection boundary -- an append-only transparency log, a
        counter-signing witness, or a third-party escrow -- would make
        already-published history rewrite-detectable and would give truncation a
        detection point. Specifying the checkpoint format, the publication
        cadence, and the verifier's obligations on a missing or divergent
        checkpoint is open work and is a prerequisite for any claim that the
        chain establishes the absence of a record.</t>
      </section>

      <section anchor="federation" numbered="true">
        <name>Cross-Administrative-Domain Trust</name>
        <t>Trust federation is implemented in the reference codebase and enables
        multiple O-Nodes operated by different organizations to participate in a
        shared trust chain via Ed25519 signatures. Open work in this area
        includes standardized key distribution and revocation for federated
        O-Nodes, public-key directory or transparency-log integration to detect
        equivocation, and policy primitives governing what
        cross-administrative-domain consumers may correlate or attest.
        Hardware-backed federation key custody
        (<xref target="hsm"/>) is a prerequisite for the highest-assurance
        multi-organization deployments.</t>
      </section>

      <section anchor="hsm" numbered="true">
        <name>Hardware Security Module Integration</name>
        <t>Approval signing already supports PKCS#11 tokens, so an approver's
        private key need not exist in host memory. The symmetric observation and
        chain keys are currently held in process memory on the O-Node.
        Integration with an HSM or TPM for those keys, or migration to an
        asymmetric construction under <xref target="asym"/>, would reduce the
        key-in-memory attack surface for the highest-assurance deployments.</t>
      </section>

      <section anchor="typedops" numbered="true">
        <name>Typed Operations</name>
        <t>A v2 observation authenticates a digest of a canonical command
        string. As stated in <xref target="obs-v2"/>, this makes the record
        meaningful only to a relying party that retains trusted request context.
        Work in progress replaces the free-form command string with a versioned
        registry of typed operations, so that the authenticated bytes cover a
        complete, self-describing operation encoding: an operation identifier, a
        registry version, typed parameters, and a resolved target. The
        properties that such a registry must satisfy are encoding injectivity
        (distinct operations encode to distinct octets), resolution determinism
        (a given operation and target resolve to the same device-level
        invocation), resolution injectivity (distinct operations do not resolve
        to the same invocation), and transport fidelity (the invocation
        delivered to the device is the one that was authenticated). Free-form
        command strings would remain supported as a legacy, lower-assurance
        adapter. This work is expected in a subsequent revision of this
        document.</t>
      </section>

      <section numbered="true">
        <name>IETF Standardization</name>
        <t>VIRP is written in RFC format and is submitted as an individual
        Internet-Draft. Experimental results for the BGP
        integration of <xref target="frr"/> are archived as a citable companion
        artifact at <xref target="VIRP-ZENODO"/>. Independent implementation by a second
        party is the prerequisite for standards track consideration; the
        open-source reference implementation is available for that purpose.</t>
      </section>
    </section>

    <section anchor="implstatus" numbered="true">
      <name>Implementation Status</name>
      <t>This section records where the reference implementation
      <xref target="VIRP-REPO"/> diverges from the normative text above, so that
      a reader is not left to infer conformance from the specification alone. It
      is expected to shrink in subsequent revisions and carries no normative
      weight. This section is intended for removal before any publication as an
      RFC.</t>
      <ul>
        <li><strong>v1 remains the default.</strong> Request parsing defaults to
        the v1 observation format; the Python API does not perform the v2
        handshake. v2 requires explicit session negotiation. Gate-rejection
        error records are v1. See <xref target="obs-v1"/>.</li>
        <li><strong>Generation is not yet transported.</strong> The
        <tt>generation</tt> value is an HKDF input but is not currently carried
        in SESSION_BIND, so an independent verifier cannot re-derive a session
        key from the authenticated transcript alone. The normative rules in
        <xref target="obs-v2"/> describe the target state; the transport change
        and the corresponding verifier check are tracked implementation
        tasks.</li>
        <li><strong>Chain emission is legacy JSON.</strong> The implementation
        emits the alphabetically ordered JSON described in
        <xref target="chainenc"/> rather than chain-format-version 1. It is
        therefore a legacy producer under this revision.</li>
        <li><strong>Oversized responses.</strong> A response exceeding the
        transport bound has been observed to be truncated and then authenticated
        as though complete. A conforming implementation MUST fail closed in this
        case, emitting an authenticated error record rather than an
        authenticated partial success. This is corrected in the
        implementation.</li>
        <li><strong>Large observations are authenticated but not chained.</strong>
        Observations above the current chain payload bound are authenticated and
        returned but not registered in the chain. Until an out-of-band artifact
        reference with a chained digest is defined, such records MUST be
        presented as authenticated-but-unchained and MUST NOT be counted as
        chain evidence.</li>
        <li><strong>v1 length bound.</strong> The v1 total-length field is 16
        bits, so the maximum conforming observation-data length is 65475 octets
        (<xref target="obs-v1"/>). An implementation that constructs a message
        of exactly 65536 octets and narrows the length to 16 bits wraps to zero;
        builders MUST enforce the 65475 bound.</li>
        <li><strong>Formal artifacts.</strong> The checked-in proof artifacts
        model the v2 derivation and acceptance path under the restriction stated
        in <xref target="formal"/>. They do not model the deployed verify
        function exactly, and comments in the artifacts that suggest otherwise
        are being corrected.</li>
      </ul>
    </section>

    <section anchor="security" numbered="true">
      <name>Security Considerations</name>
      <t>Security considerations are distributed through this document rather
      than confined to a single section. <xref target="keys"/> and
      <xref target="custody"/> state what each key role does and does not
      protect, and are the authoritative statement of scope.
      <xref target="threat"/> states the threat model per key holder.
      <xref target="goals"/> states the informal goals and
      <xref target="formal"/> states exactly what has and has not been
      machine-verified. <xref target="implstatus"/> states where the reference
      implementation currently diverges from the normative text.</t>
      <t>Three properties are important enough to restate here. First,
      observation authentication in this revision is symmetric: it establishes
      integrity relative to the secrecy of a key that the verifier also holds,
      and therefore does not provide publicly verifiable origin or any defence
      against a party holding that key. Second, authentication establishes that
      the collection boundary obtained and authenticated those octets for that
      recorded request; it does not certify that the response reflects the
      managed device's true state, and a compromised boundary can produce
      well-formed records that do. Third, the chain establishes linkage of the
      entries presented; establishing that a record does not exist requires an
      independently retained or externally anchored history
      (<xref target="anchoring"/>), and no claim of the form "no approval was
      granted" is supported by an unanchored local chain.</t>
      <t>Deployments SHOULD carry VIRP traffic over an authenticated, encrypted
      transport. The protocol provides neither confidentiality nor endpoint
      authentication for the transport itself.</t>
    </section>

    <section anchor="iana" numbered="true">
      <name>IANA Considerations</name>
      <t>This document has no IANA actions.</t>
    </section>

    <section anchor="ack" numbered="true">
      <name>Acknowledgements</name>
      <t>The author thanks Adrian Farrel, whose review of draft-howard-virp-03
      prompted the reframing of VIRP to a trust primitive for any operator
      acting on production infrastructure.</t>
      <t>The author thanks Usama Sardar (TU Dresden) for the ProVerif formal
      analysis and ongoing work toward discharging the replay-counter
      restriction.</t>
      <t>The author thanks an independent reviewer who exercised the
      implementation on physical Cisco hardware and reported the issues
      discovered during that evaluation.</t>
    </section>

  </middle>

  <back>
    <references>
      <name>References</name>

      <references>
        <name>Normative References</name>

        <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author initials="S." surname="Bradner" fullname="S. Bradner"/>
            <date year="1997" month="March"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
          <seriesInfo name="DOI" value="10.17487/RFC2119"/>
        </reference>

        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author initials="B." surname="Leiba" fullname="B. Leiba"/>
            <date year="2017" month="May"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
          <seriesInfo name="DOI" value="10.17487/RFC8174"/>
        </reference>

        <reference anchor="RFC2104" target="https://www.rfc-editor.org/info/rfc2104">
          <front>
            <title>HMAC: Keyed-Hashing for Message Authentication</title>
            <author initials="H." surname="Krawczyk" fullname="H. Krawczyk"/>
            <author initials="M." surname="Bellare" fullname="M. Bellare"/>
            <author initials="R." surname="Canetti" fullname="R. Canetti"/>
            <date year="1997" month="February"/>
          </front>
          <seriesInfo name="RFC" value="2104"/>
          <seriesInfo name="DOI" value="10.17487/RFC2104"/>
        </reference>

        <reference anchor="RFC5869" target="https://www.rfc-editor.org/info/rfc5869">
          <front>
            <title>HMAC-based Extract-and-Expand Key Derivation Function (HKDF)</title>
            <author initials="H." surname="Krawczyk" fullname="H. Krawczyk"/>
            <author initials="P." surname="Eronen" fullname="P. Eronen"/>
            <date year="2010" month="May"/>
          </front>
          <seriesInfo name="RFC" value="5869"/>
          <seriesInfo name="DOI" value="10.17487/RFC5869"/>
        </reference>

        <reference anchor="RFC6234" target="https://www.rfc-editor.org/info/rfc6234">
          <front>
            <title>US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF)</title>
            <author initials="D." surname="Eastlake 3rd" fullname="D. Eastlake 3rd"/>
            <author initials="T." surname="Hansen" fullname="T. Hansen"/>
            <date year="2011" month="May"/>
          </front>
          <seriesInfo name="RFC" value="6234"/>
          <seriesInfo name="DOI" value="10.17487/RFC6234"/>
        </reference>

        <reference anchor="RFC8032" target="https://www.rfc-editor.org/info/rfc8032">
          <front>
            <title>Edwards-Curve Digital Signature Algorithm (EdDSA)</title>
            <author initials="S." surname="Josefsson" fullname="S. Josefsson"/>
            <author initials="I." surname="Liusvaara" fullname="I. Liusvaara"/>
            <date year="2017" month="January"/>
          </front>
          <seriesInfo name="RFC" value="8032"/>
          <seriesInfo name="DOI" value="10.17487/RFC8032"/>
        </reference>
      </references>

      <references>
        <name>Informative References</name>

        <reference anchor="ED25519">
          <front>
            <title>High-speed high-security signatures</title>
            <author initials="D.J." surname="Bernstein" fullname="D. J. Bernstein"/>
            <date year="2011"/>
          </front>
          <refcontent>Journal of Cryptographic Engineering</refcontent>
        </reference>

        <reference anchor="I-D.howard-virp" target="https://datatracker.ietf.org/doc/html/draft-howard-virp-05">
          <front>
            <title>VIRP: Verified Infrastructure Response Protocol</title>
            <author initials="N." surname="Howard" fullname="N. Howard"/>
            <date year="2026" month="May" day="21"/>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-howard-virp-05"/>
          <refcontent>Work in Progress</refcontent>
        </reference>

        <reference anchor="NIST-ZTA">
          <front>
            <title>Zero Trust Architecture</title>
            <author>
              <organization>National Institute of Standards and Technology</organization>
            </author>
            <date year="2020"/>
          </front>
          <seriesInfo name="NIST" value="SP 800-207"/>
        </reference>

        <reference anchor="PROVERIF" target="https://bblanche.gitlabpages.inria.fr/proverif/">
          <front>
            <title>ProVerif: Cryptographic protocol verifier in the formal model</title>
            <author initials="B." surname="Blanchet" fullname="B. Blanchet"/>
            <date year="2026"/>
          </front>
        </reference>

        <reference anchor="THIRDLEVEL" target="https://thirdlevel.ai">
          <front>
            <title>Third Level IT LLC Research</title>
            <author>
              <organization>Third Level IT LLC</organization>
            </author>
            <date year="2026"/>
          </front>
        </reference>

        <reference anchor="VIRP-BGP-FRR" target="https://github.com/nhowardtli/virp-bgp-frr">
          <front>
            <title>VIRP BGP Module: FRR Integration Reference Implementation</title>
            <author initials="N." surname="Howard" fullname="N. Howard"/>
            <date year="2026"/>
          </front>
          <refcontent>Apache 2.0 License</refcontent>
        </reference>

        <reference anchor="VIRP-FINDING-003">
          <front>
            <title>VIRP-FINDING-003: Agent Self-Reported Security Bypasses</title>
            <author initials="N." surname="Howard" fullname="N. Howard"/>
            <date year="2026"/>
          </front>
          <refcontent>Third Level IT LLC Internal Research</refcontent>
        </reference>

        <reference anchor="VIRP-REPO" target="https://github.com/nhowardtli/virp">
          <front>
            <title>VIRP Reference Implementation</title>
            <author initials="N." surname="Howard" fullname="N. Howard"/>
            <date year="2026"/>
          </front>
          <refcontent>Apache 2.0 License</refcontent>
        </reference>

        <reference anchor="VIRP-ZENODO" target="https://doi.org/10.5281/zenodo.19161493">
          <front>
            <title>Cryptographic Observation Attestation Inside a BGP Routing
            Daemon: Experimental Validation of VIRP</title>
            <author initials="N." surname="Howard" fullname="Nathan Howard"/>
            <date year="2026" month="March" day="22"/>
          </front>
          <seriesInfo name="DOI" value="10.5281/zenodo.19161493"/>
          <refcontent>Zenodo, Technical Note, version 1.0</refcontent>
        </reference>
      </references>
    </references>

    <section anchor="changes" numbered="true">
      <name>Changes from draft-howard-virp-05</name>
      <t>This revision narrows several security claims relative to
      <xref target="I-D.howard-virp"/>. The narrowing is
      deliberate and reconciles the specification with the reference
      implementation. A reviewer comparing the two revisions should expect to
      find claims removed.</t>

      <ol>
        <li><strong>Per-observation Ed25519 removed.</strong> Revision 05
        described a dual-signature observation carrying both an HMAC-SHA256 code
        and an Ed25519 signature, and an <tt>ed25519</tt> field in the
        observation wire format. No such signature exists: observations are
        authenticated with HMAC-SHA256 only. Ed25519 is used for approval
        records and federation material. The field is removed from the wire
        format and the claim is removed from the abstract, the threat model, and
        the security goals.</li>
        <li><strong>Symmetric trust boundary stated.</strong> Because
        observation authentication is symmetric, a verifier holds a forging
        capability. The abstract, <xref target="keys"/>,
        <xref target="custody"/>, and <xref target="goals"/> now state this
        directly. Revision 05 described the verifier as unable to forge, which
        is true only of an asymmetric construction.</li>
        <li><strong>Tamarin claim withdrawn.</strong> Revision 05 stated
        machine verification in both ProVerif and Tamarin. Only a ProVerif model
        exists. <xref target="formal"/> now states the ProVerif result, the
        trace restriction it depends on, what is not modeled (timestamp
        freshness, the high-water counter, the v1 path), and describes Tamarin
        as future work.</li>
        <li><strong>Formal result strengthened in one direction.</strong>
        Revision 05 claimed non-injective agreement. Injective agreement now
        holds, under the stated trace restriction.</li>
        <li><strong>G1 restated for HMAC.</strong> Revision 05 reduced G1 to
        EUF-CMA unforgeability of Ed25519, a primitive observations do not use.
        G1 is now stated per key under the PRF/MAC assumption on HMAC-SHA256,
        with the O-Key dual-role exception named explicitly.</li>
        <li><strong>Two observation formats specified.</strong> Revision 05
        described one format. There are two: a deprecated stateless v1
        (<xref target="obs-v1"/>) and a session-bound v2
        (<xref target="obs-v2"/>). Both byte layouts and their validation rules
        are now normative. v1 remains the reference default, which is recorded
        rather than claimed as resolved.</li>
        <li><strong>Command binding scoped.</strong> A v2 observation
        authenticates a SHA-256 digest of the canonical command, not the command
        text. It is therefore not a self-contained receipt and binds a command
        only relative to a verifier holding trusted request context. The
        abstract and <xref target="obs-v2"/> state this;
        <xref target="typedops"/> describes the intended fix.</li>
        <li><strong>Key roles enumerated.</strong> <xref target="keys"/>
        replaces the single-key description with the O-Key, R-Key, session key,
        K_chain, approver key, and federation key, states which are distinct,
        and states the one current exception: the O-Key both authenticates v1
        and is the HKDF input for v2.</li>
        <li><strong>Chain entry encoding specified.</strong>
        <xref target="chainenc"/> defines chain-format-version 1 as a
        deterministic binary encoding and designates the JSON the reference
        implementation currently emits as a legacy, non-conforming format.</li>
        <li><strong>Chain claims qualified.</strong> Chain integrity covers the
        entries presented, under K_chain. Detecting truncation or establishing
        the absence of a record requires an independently retained head or
        external anchoring (<xref target="anchoring"/>). Revision 05's
        implication that a local chain can establish absence is removed.</li>
        <li><strong>Threat model split per key.</strong> The single
        "compromised verification component" row is replaced with rows for each
        key holder, because different holders have different forging
        power.</li>
        <li><strong>Terminology.</strong> "Signed" is reserved for Ed25519;
        HMAC-protected records are described as "authenticated" throughout.</li>
        <li><strong>Implementation status separated.</strong> Divergences
        between the specification and the reference implementation are collected
        in <xref target="implstatus"/> rather than distributed through the
        normative text.</li>
        <li><strong>Acknowledgements.</strong> One acknowledgement from
        revision 05 has been removed because the attribution could not be
        substantiated. A second is stated without identifying detail pending the
        reviewer's consent.</li>
      </ol>

      <t>Work not in this revision, and expected in a subsequent one: the typed
      operation registry described in <xref target="typedops"/>; reconciliation
      of <xref target="containment"/> and <xref target="impl"/> with the current
      reference deployment; and an assurance-level framework distinguishing
      deployments by which of these properties they actually enforce.</t>
    </section>
  </back>
</rfc>
