Skip to content

Repository files navigation

IntentGate

IntentGate is a macOS prototype for approving high-impact software-agent actions with a YubiKey 5.8. Instead of asking a person to approve a generic sign-in, it binds the approval to the exact operation the software is about to perform.

The example in this repository is a vendor payment. A local dashboard creates the request, the native signer displays the amount and destination, and a YubiKey signs the SHA-256 digest of the canonical payment manifest using previewSign. The simulated payment is released only if the signed request still matches the final execution payload.

Why?

Authentication answers “which credential was used?” It does not automatically answer “what did the person agree to?” That difference becomes important when an agent can prepare actions with real consequences.

I built IntentGate to make that boundary visible. The normal path shows an approved operation being released. The integrity-test path changes the amount after approval: the YubiKey credential and signature are still valid, but the release is blocked because the final operation is no longer the one that was reviewed.

I kept the upstream side deterministic on purpose. The dashboard stands in for an invoice agent, but the demo does not need a live model or an external payment API. That let me spend the hackathon on the authorization and verification flow rather than on prompt behavior or third-party setup.

Quick start

Requirements

  • macOS 14 or newer
  • Xcode 16.3 or newer (the included packages use Swift tools 6.1)
  • Python 3.10 or newer
  • A YubiKey 5.8 that reports previewSign support

The Swift dependencies are included under Vendor/. Installing the Python dependency is the only setup step that needs network access.

1. Install the server dependency

From the project root:

python3 -m venv IntentGateServer/.venv
IntentGateServer/.venv/bin/python -m pip install -r IntentGateServer/requirements.txt

2. Run the signer

  1. Open IntentGate.xcodeproj in Xcode.
  2. Select the IntentGateSigner scheme and the My Mac destination.
  3. Press Run.
  4. In IntentGate, press Start Server, then Dashboard.
  5. Select the YubiKey 5.8 marked previewSign: available and press Register YubiKey. Enter its FIDO PIN and touch it when prompted.
  6. Create a payment in the dashboard, return to the signer, review it, and press Approve & Sign.

When several YubiKeys are connected, IntentGate lists all FIDO HID devices and binds the ceremony to the one you selected.

Demo walkthrough

Normal approval

  1. Leave Change the final amount off.
  2. Create the default $8,400.00 payment in the dashboard.
  3. Review the amount, destination, invoice, expiry, and manifest hash in the signer.
  4. Approve the request and touch the selected YubiKey.
  5. The receipt shows that the WebAuthn credential, previewSign signature, and final payload checks passed.

Changed payload

  1. Reset the dashboard.
  2. Turn on Change the final amount.
  3. Create and approve the same payment.
  4. IntentGate changes the simulated execution amount from $8,400.00 to $84,000.00 after approval.
  5. The receipt shows a valid credential and signature, but different approved and execution hashes, so the release is blocked.

This second path is the main idea behind the project: a valid signature is only useful for the operation it actually covers.

How it works

sequenceDiagram
    participant Dashboard as Web dashboard
    participant Server as IntentGate server
    participant Signer as macOS signer
    participant Key as YubiKey 5.8

    Dashboard->>Server: Create canonical payment manifest
    Signer->>Server: Poll for a pending request
    Server-->>Signer: Manifest, WebAuthn challenge, and ARKG arguments
    Signer->>Signer: Re-encode and hash the displayed manifest
    Signer->>Key: WebAuthn assertion + previewSign digest
    Key-->>Signer: Credential response + operation signature
    Signer->>Server: Submit the approval response
    Server->>Server: Verify credential, signature, expiry, nonce, and final payload
    Server-->>Dashboard: Release or block with a receipt
Loading

The server encodes each PaymentManifest as canonical JSON and stores those exact bytes and their SHA-256 hash. Before opening a YubiKey ceremony, the signer decodes the bytes for display, re-encodes the manifest, and verifies that the displayed model and server-provided hash still agree.

The release gate checks all of the following:

  1. The registered WebAuthn credential produced the assertion.
  2. The assertion contains a previewSign result and its signature verifies with the ARKG-derived public key.
  3. The signed digest belongs to the canonical payment manifest shown in the signer.
  4. The request has not expired or already been completed.
  5. The final execution payload has the same canonical bytes and hash as the approved manifest.
  6. Only then is the simulated payment marked as released.

The browser is not treated as an identity provider, and IntentGate does not claim to prove who originally requested the payment. Its job is to show the request and the release decision. The YubiKey ceremony proves that the registered signer approved the exact manifest.

YubiKey selection

The supplied macOS FIDO UI connected to the first available HID device. I added a narrow device-selection path so IntentGate can enumerate connected keys, show their firmware and capabilities, and open the selected device for every later ceremony.

The identifier available through macOS HID is a location ID, not a YubiKey serial number. Moving a key to a different USB port can therefore make it appear to be a different device. IntentGate fails closed in that case and asks you to reconnect or register a different key. The exact changes to the supplied packages are documented in Vendor/README.md.

Project structure

IntentGate/
├── IntentGate.xcodeproj/          Supported Xcode build
├── Sources/IntentGateSigner/      SwiftUI signer, API client, and YubiKey flow
├── IntentGateServer/server/       Local relying party, release gate, and dashboard
├── IntentGateServer/Tests/        Python unit and HTTP smoke tests
├── Resources/                     App Info.plist and entitlements
├── Vendor/                        Included YubiKit and FidoUI source + licenses
└── Package.swift                  Optional SwiftPM build of the signer target

The Xcode project is the normal way to run the app. Package.swift points at the same local source packages and provides a second reproducible compile path.

Server lifecycle

The signer starts the Python service as a child process and stops it when the app quits normally. It does not install a launch agent or background daemon. If the app is force-quit, the next launch only cleans up a process that matches the IntentGate server command and project folder; an unrelated process using port 8787 is left alone and reported as a conflict.

For server-only debugging, run:

cd IntentGateServer
.venv/bin/python -m server.app --host 127.0.0.1 --port 8787

The API and dashboard are then available at http://127.0.0.1:8787.

Limitations

IntentGate is a local hackathon prototype, not a payment product.

  • The payment executor is simulated; no money moves and no payment provider is contacted.
  • Requests, enrollment, and receipts are stored in memory and disappear when the server stops.
  • The relying-party configuration is for a local demonstration (localhost and 127.0.0.1).
  • The flow follows the user-presence model in the supplied quickstart. It is not a complete account or workforce identity system.
  • There is no durable audit log, remote attestation policy, multi-user approval, or production policy engine.
  • previewSign is a YubiKey 5.8 preview API. This project explores an authorization pattern; it does not define a production protocol around that API.

Troubleshooting

The server stops before becoming ready

Recreate the local Python environment:

python3 -m venv IntentGateServer/.venv
IntentGateServer/.venv/bin/python -m pip install -r IntentGateServer/requirements.txt

If port 8787 is already occupied by another program, IntentGate reports the conflict instead of terminating that process.

No compatible YubiKey appears

Connect the YubiKey over USB, press Refresh, and choose a device marked previewSign: available. Older firmware may appear in the list but cannot complete the operation-signing ceremony.

Acknowledgments

IntentGate uses the YubiKit, FidoUI, and Python FIDO2 work provided by Yubico for the YubiKey 5.8 developer program. The included YubiKit and FidoUI licenses and the small project-specific modifications are documented under Vendor/.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages