Skip to content

docs: Document Aura HITL approval webhook HMAC signing - #91

Open
promptless[bot] wants to merge 1 commit into
mainfrom
promptless/aura-hitl-webhook-hmac
Open

promptless[bot] wants to merge 1 commit into
mainfrom
promptless/aura-hitl-webhook-hmac

Conversation

@promptless

@promptless promptless Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Open in Promptless

Aura added an opt-in HMAC-SHA256 "root of trust" for the human-in-the-loop (HITL) approval webhook exchange (mezmo/aura PR #491), which verifies that the party returning an approval decision is the same party Aura sent the request to. The Aura docs did not cover it, and the HITL page still stated that webhook egress had "no built-in authentication layer."

This suggestion documents the feature on two existing Aura pages:

  • aura/hitl.mdx: a new "Sign and Verify Approval Webhooks" section covering how to enable signing with the AURA_HITL_WEBHOOK_SECRET* environment variables (32-byte minimum, openssl rand -hex 32, fail-loud on an empty secret, HTTPS required when signing is on, read once at startup), the request signature and header contract Aura sends, the responder's obligation to sign its decision on both the webhook response and the POST /v1/approvals/{decision_id} route, the invalid-signature outcomes, and a zero-downtime key rotation procedure. It also updates the "Conversational route" note and replaces the now-outdated "Current limitations" bullet.
  • aura/configuration-reference.mdx: a concise "Webhook Signing" subsection under [hitl] documenting the three environment variables, framed as environment-variable configuration, with a cross-link to the HITL page.

No navigation change is needed because aura/hitl is already in the sidebar.

Trigger Events


Tip: Use Slack message actions (⋯ menu → Update Docs) to capture doc updates without interrupting conversations 💬

Document the opt-in HMAC-SHA256 root of trust for the Aura HITL approval
webhook exchange added in mezmo/aura PR #491. Covers enabling signing via the
AURA_HITL_WEBHOOK_SECRET* environment variables, the request/decision signature
contract and header shape, the responder signing obligation for both approval
routes, and the zero-downtime key rotation procedure.

Relates to: mezmo/aura PR #491
Comment thread aura/hitl.mdx

### Enable Signing

To enable signing, set the `AURA_HITL_WEBHOOK_SECRET` environment variable. The

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AURA_HITL_WEBHOOK_SECRET (primary, enables signing), AURA_HITL_WEBHOOK_SECRET_SECONDARY (verify-only), AURA_HITL_WEBHOOK_TOLERANCE_SECS (default 300, max 86400) and MIN_SECRET_BYTES=32 constants.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/signing.rs#L36-L47

Comment thread aura/hitl.mdx
`openssl rand -hex 32`, which produces a 64-character value used as-is with no
decoding step.

A present-but-empty or whitespace-only secret is a hard startup error, so Aura

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_from_env: unset primary = feature off (warn! log "HITL webhook HMAC verification DISABLED"); present-but-empty/whitespace primary = ConfigError::PrimaryTooShort; secondary without primary = ConfigError::SecondaryWithoutPrimary; successful load emits info! "HITL webhook HMAC signing and verification enabled".

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/signing.rs#L309-L357

Comment thread aura/hitl.mdx
A present-but-empty or whitespace-only secret is a hard startup error, so Aura
fails loudly rather than silently disabling signing. When a secret is set
together with an `http://` (unencrypted) webhook URL, Aura refuses to start.
HTTPS is required when signing is on. Secrets are read once at startup, so

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Secrets loaded once at startup via WebhookHmac::load_from_env(); a config error fails server boot. Same once-at-startup load pattern in crates/aura-cli/src/backend/direct.rs#L94-L103.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura-web-server/src/main.rs#L283-L308

@mintlify

mintlify Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
mezmo-docs 🟢 Ready View Preview Aug 18, 2026, 5:01 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment thread aura/hitl.mdx

A present-but-empty or whitespace-only secret is a hard startup error, so Aura
fails loudly rather than silently disabling signing. When a secret is set
together with an `http://` (unencrypted) webhook URL, Aura refuses to start.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_webhook_signing_config: with an HMAC secret configured, an http:// webhook URL fails at boot with PlaintextWebhookUrlError; HTTPS required when signing is on.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/route.rs#L469-L498

Comment thread aura/hitl.mdx
Aura's outbound approval-request POST carries two headers:

```http
X-Aura-Signature-256: sha256=<64 lowercase hex>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header contract: X-Aura-Signature-256: sha256=<64 lowercase hex chars>, X-Aura-Timestamp: .

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/signing.rs#L8-L12

Comment thread aura/hitl.mdx
```

The signed string is the canonical form
`{unix_timestamp}.{context}.{raw_request_body}`. Aura computes HMAC-SHA256 over

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Canonical signed payload "{timestamp}.{context}.{body}" via keyed_mac, shared by sign() and verify() so the two legs cannot drift on payload encoding. Context labels: egress request "approval-request:{decision_id}" (route.rs#L381-L383), decision "approval-decision:{decision_id}" (route.rs#L419-L421, handlers.rs#L1141).

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/signing.rs#L548-L565

Comment thread aura/hitl.mdx
that string with the secret and hex-encodes the result in lowercase. The approval
request uses the context `approval-request:{decision_id}`.

`raw_request_body` is the exact bytes of the request body as sent on the wire.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ingress endpoint buffers raw bytes via the Bytes extractor and calls authorize_ingress() before any JSON parse; only after verification does it re-parse through the stock Json extractor.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura-web-server/src/handlers.rs#L1103-L1181

Comment thread aura/hitl.mdx
```text
signed_string = "{timestamp}.{context}.{raw_body}"
signature = "sha256=" + lowercase_hex(hmac_sha256(secret, signed_string))
# Verify an inbound request: recompute with context "approval-request:{decision_id}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify(): checks timestamp skew against tolerance, then evaluates primary and secondary candidates via constant-time verify_slice before returning Ok/Mismatch.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/signing.rs#L390-L421

Comment thread aura/hitl.mdx

| Where the signature check fails | Result |
|---|---|
| Webhook response leg | The tool fails closed. The worker receives an approval channel error. |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Webhook response leg: signature verification failure returns ApprovalError::ResponseUnverified, which tool.rs maps to a generic Err(ToolError::ToolCallError) tool failure — same generic-error path as the pre-existing BadStatus/Parse rows on this page.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/route.rs#L404-L436

Comment thread aura/hitl.mdx
| Where the signature check fails | Result |
|---|---|
| Webhook response leg | The tool fails closed. The worker receives an approval channel error. |
| Approval ingress endpoint (`POST /v1/approvals/{decision_id}`) | Aura returns a uniform `401`. |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unauthorized_response(): every ingress verification failure (missing/malformed headers, skew, mismatch) maps to the same uniform 401.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura-web-server/src/handlers.rs#L1183-L1192

Comment thread aura/hitl.mdx
decisions. Complete this on all instances before continuing.
2. Update each responder to sign its decisions with the new key. During this
window, keep each responder able to verify Aura's requests with both the old
and new keys, because Aura still signs its outbound requests with the old key

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sign() computes the outbound HMAC using only self.primary — the secondary key is never used for signing, only for verification (L390-L421) — supporting the rotation guidance that Aura keeps signing outbound requests with the primary key until it is promoted.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/signing.rs#L364-L383


| Environment variable | Default | Description |
|---|---|---|
| `AURA_HITL_WEBHOOK_SECRET` | none (signing off) | Primary HMAC key. Presence enables signing. Raw UTF-8 bytes are used as the key, with a minimum of 32 bytes. An empty or whitespace-only value is a startup error. |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same three env vars and defaults (AURA_HITL_WEBHOOK_SECRET, _SECONDARY, _TOLERANCE_SECS default 300 max 86400) reused in the configuration-reference table.

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/signing.rs#L36-L47

| `AURA_HITL_WEBHOOK_SECRET_SECONDARY` | none | Optional second key, used for verification only, to support zero-downtime key rotation. Setting it without a primary is a startup error. |
| `AURA_HITL_WEBHOOK_TOLERANCE_SECS` | `300` | Allowed timestamp skew window, in seconds. Valid range is 1 to 86400. |

When a secret is set together with an `http://` (unencrypted) webhook URL, Aura

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boot-time validate_webhook_signing_config rejects an http:// webhook URL when a secret is configured (PlaintextWebhookUrlError).

Source: https://github.com/mezmo/aura/blob/35483b9d2fadb24a09c4be74ef17d47678209f5a/crates/aura/src/hitl/route.rs#L478-L498

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants