vestauth 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +191 -96
- package/package.json +4 -7
- package/src/cli/actions/agent/curl.js +2 -1
- package/src/cli/actions/primitives/verify.js +0 -1
- package/src/cli/commands/agent.js +0 -1
- package/src/lib/helpers/errors.js +10 -0
- package/src/lib/helpers/headers.js +0 -12
- package/src/lib/primitives.js +1 -3
- package/src/lib/helpers/executeCommand.js +0 -115
- package/src/lib/helpers/hash.js +0 -8
- package/src/lib/helpers/verifyWebBotAuth.js +0 -25
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
[Unreleased](https://github.com/vestauth/vestauth/compare/v0.8.
|
|
5
|
+
[Unreleased](https://github.com/vestauth/vestauth/compare/v0.8.4...main)
|
|
6
|
+
|
|
7
|
+
## [0.8.4](https://github.com/vestauth/vestauth/compare/v0.8.3...v0.8.4) (2026-02-06)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
* Patch bug with missing `web-bot-auth` lib
|
|
12
|
+
|
|
13
|
+
## [0.8.3](https://github.com/vestauth/vestauth/compare/v0.8.2...v0.8.3) (2026-02-06)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
* Various clean up.
|
|
6
18
|
|
|
7
19
|
## [0.8.2](https://github.com/vestauth/vestauth/compare/v0.8.1...v0.8.2) (2026-02-05)
|
|
8
20
|
|
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
*auth for agents*–from the creator of [`dotenv`](https://github.com/motdotla/dotenv) and [`dotenvx`](https://github.com/dotenvx/dotenvx).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
> [1 minute demo 📺](https://www.youtube.com/watch?v=cHARyULr_qk)
|
|
6
|
+
>
|
|
7
|
+
> Vestauth gives agents a cryptographic identity and a simple way to authenticate HTTP requests. Most agent systems rely on API keys, bearer tokens, or username/passwords. These approaches are difficult to rotate, easy to leak, and hard to attribute to a specific agent. Vestauth replaces shared secrets with public/private key cryptography. Agents sign requests using a private key, and providers verify those requests using the agent's public key. [[1](#compare)]
|
|
8
|
+
>
|
|
9
|
+
> *Scott Motte–creator of `dotenv` and `dotenvx`*
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ npm i -g vestauth
|
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
20
|
vestauth agent init
|
|
21
|
-
vestauth agent curl https://
|
|
21
|
+
vestauth agent curl -X POST https://ping.vestauth.com
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
<details><summary>with curl 🌐 </summary><br>
|
|
@@ -187,19 +187,6 @@ Vestauth intentionally separates identity discovery from verification to support
|
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
|
|
190
|
-
## Standards
|
|
191
|
-
|
|
192
|
-
Vestauth builds on open internet standards for agent authentication.
|
|
193
|
-
|
|
194
|
-
| Specification | Purpose |
|
|
195
|
-
|------------|------------|
|
|
196
|
-
| **[RFC 9421 – HTTP Message Signatures](https://datatracker.ietf.org/doc/rfc9421/)** | Defines how requests are cryptographically signed and verified |
|
|
197
|
-
| **[Web-Bot-Auth Draft](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture)** | Defines headers and authentication architecture for autonomous agents |
|
|
198
|
-
|
|
199
|
-
Vestauth follows these specifications to ensure interoperability between agents and providers while avoiding vendor lock-in. Vestauth focuses on developer ergonomics while staying compliant with these emerging standards.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
190
|
## Advanced
|
|
204
191
|
|
|
205
192
|
> Become a `vestauth` power user.
|
|
@@ -209,6 +196,101 @@ Vestauth follows these specifications to ensure interoperability between agents
|
|
|
209
196
|
|
|
210
197
|
Advanced CLI commands.
|
|
211
198
|
|
|
199
|
+
<details><summary>`agent init`</summary><br>
|
|
200
|
+
|
|
201
|
+
Create agent.
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
$ vestauth agent init
|
|
205
|
+
✔ agent created (.env/AGENT_ID=agent-609a4fd2ebf4e6347108c517)
|
|
206
|
+
⮕ next run: [vestauth agent curl https://api.vestauth.com/whoami]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
</details>
|
|
210
|
+
<details><summary>`agent curl`</summary><br>
|
|
211
|
+
|
|
212
|
+
Run curl as agent.
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
$ vestauth agent curl https://api.vestauth.com/whoami
|
|
216
|
+
{"uid":"agent-609a4fd2ebf4e6347108c517", ...}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
</details>
|
|
220
|
+
<details><summary>`agent curl --pretty-print`</summary><br>
|
|
221
|
+
|
|
222
|
+
Pretty print curl json output.
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
$ vestauth agent curl https://api.vestauth.com/whoami --pp
|
|
226
|
+
{
|
|
227
|
+
"uid": "agent-609a4fd2ebf4e6347108c517",
|
|
228
|
+
"kid": "FGzgs758DBGnI1S0BejChDsK0IKZm3qPpOOXdRnnBkM",
|
|
229
|
+
"public_jwk": {
|
|
230
|
+
...
|
|
231
|
+
},
|
|
232
|
+
"well_known_url": "https://agent-609a4fd2ebf4e6347108c517.agents.vestauth.com/.well-known/http-message-signatures-directory"
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
</details>
|
|
237
|
+
|
|
238
|
+
<details><summary>`agent headers`</summary><br>
|
|
239
|
+
|
|
240
|
+
Generate signed headers as agent.
|
|
241
|
+
|
|
242
|
+
```sh
|
|
243
|
+
$ vestauth agent headers GET https://api.vestauth.com/whoami --pp
|
|
244
|
+
{
|
|
245
|
+
"Signature": "sig1=:UW6A7j8jo+gQxd+EeVgDddY51ZOc9plrSaupW/N53hQnQFvP9BuwQHgL7SVPLQIu4cnRzLgvwm7Yu9YMO+HUDQ==:",
|
|
246
|
+
"Signature-Input": "sig1=(\"@authority\");created=1770396357;keyid=\"FGzgs758DBGnI1S0BejChDsK0IKZm3qPpOOXdRnnBkM\";alg=\"ed25519\";expires=1770396657;nonce=\"PrE7A6I_5fWnxBsBigNvxjp3-YangXl71V1uM3hPZavh918JqzjMSRcjHv_n5XIb3N8WivZEeigCBH6QGDSqgA\";tag=\"web-bot-auth\"",
|
|
247
|
+
"Signature-Agent": "sig1=agent-609a4fd2ebf4e6347108c517.agents.vestauth.com"
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
</details>
|
|
252
|
+
|
|
253
|
+
<details><summary>`agent headers --id`</summary><br>
|
|
254
|
+
|
|
255
|
+
Change the `AGENT_ID`.
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
$ vestauth agent headers GET https://api.vestauth.com/whoami --id agent-1234 --pp
|
|
259
|
+
{
|
|
260
|
+
"Signature": "sig1=:UW6A7j8jo+gQxd+EeVgDddY51ZOc9plrSaupW/N53hQnQFvP9BuwQHgL7SVPLQIu4cnRzLgvwm7Yu9YMO+HUDQ==:",
|
|
261
|
+
"Signature-Input": "sig1=(\"@authority\");created=1770396357;keyid=\"FGzgs758DBGnI1S0BejChDsK0IKZm3qPpOOXdRnnBkM\";alg=\"ed25519\";expires=1770396657;nonce=\"PrE7A6I_5fWnxBsBigNvxjp3-YangXl71V1uM3hPZavh918JqzjMSRcjHv_n5XIb3N8WivZEeigCBH6QGDSqgA\";tag=\"web-bot-auth\"",
|
|
262
|
+
"Signature-Agent": "sig1=agent-1234.agents.vestauth.com"
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
</details>
|
|
267
|
+
|
|
268
|
+
<details><summary>`agent headers --private-jwk`</summary><br>
|
|
269
|
+
|
|
270
|
+
Change the `AGENT_PRIVATE_JWK` used to sign the headers.
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
$ vestauth agent headers GET https://api.vestauth.com/whoami --private-jwk '{"crv":"Ed25519","d":"RyFk7QTOk_bMjFQKjyAR-vJDp7BITn9U0YBFNdpR9wE","x":"hyAxNMbuTcFQq420Dr46ucF0dRZ_FIyxgsujruEoklM","kty":"OKP","kid":"UfHTArlyLsqM8cB8sNfH2z6XOwc0RmJIq2CAPGfvMjk"}' --pp
|
|
274
|
+
{
|
|
275
|
+
"Signature": "sig1=:PZUVVjqiECYuk8Hg1GZKKeJmwhLrcRdRA7nm1R595UFK9cx0q9atNFBzKP5wBEmszMIgvpYdMrIQbPEeKz4tCQ==:",
|
|
276
|
+
"Signature-Input": "sig1=(\"@authority\");created=1770396546;keyid=\"UfHTArlyLsqM8cB8sNfH2z6XOwc0RmJIq2CAPGfvMjk\";alg=\"ed25519\";expires=1770396846;nonce=\"BSIugautfZvN3u5QUgl1mMuyxgmeRsRy9XxX7GXxjJxq1mI0kJl4F-C1nITtOfSeEt6xR1YBfyxsffNKy_wKSA\";tag=\"web-bot-auth\"",
|
|
277
|
+
"Signature-Agent": "sig1=agent-609a4fd2ebf4e6347108c517.agents.vestauth.com"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
</details>
|
|
282
|
+
|
|
283
|
+
<details><summary>`provider verify`</summary><br>
|
|
284
|
+
|
|
285
|
+
Verify agent.
|
|
286
|
+
|
|
287
|
+
```sh
|
|
288
|
+
$ vestauth provider verify GET https://api.vestauth.com/whoami --signature "sig1=:H1kxwSRWFbIzKbHaUy4hQFp/JrmVTX//72JPHcW4W7cPt9q6LytRJgx5pUgWrrr7DCcMWgx/jpTPc8Ht8SZ3CQ==:" --signature-input "sig1=(\"@authority\");created=1770396709;keyid=\"FGzgs758DBGnI1S0BejChDsK0IKZm3qPpOOXdRnnBkM\";alg=\"ed25519\";expires=1770397009;nonce=\"BZSDVktdkjO6XH5jafAdPDttsB6eytXO7u8KXJN1tMtd5bprE3rp08HiaTRo7H6gZGtYb4_qtL7RiGi8P2Gq7w\";tag=\"web-bot-auth\"" --signature-agent "sig1=agent-609a4fd2ebf4e6347108c517.agents.vestauth.com"
|
|
289
|
+
{"uid":"agent-609a4fd2ebf4e6347108c517",...}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
</details>
|
|
293
|
+
|
|
212
294
|
<details><summary>`primitives keypair`</summary><br>
|
|
213
295
|
|
|
214
296
|
Generate public/private keypair.
|
|
@@ -252,112 +334,125 @@ $ vestauth primitives headers GET http://example.com --pp
|
|
|
252
334
|
Verify signed headers.
|
|
253
335
|
|
|
254
336
|
```sh
|
|
255
|
-
$ vestauth primitives verify GET https://
|
|
256
|
-
{"
|
|
337
|
+
$ vestauth primitives verify GET https://api.vestauth.com/whoami --signature "sig1=:UHqXQbWZmyYW40JRcdCl+NLccLgPmcoirUKwLtdcpEcIgxG2+i+Q2U3yIYeMquseON3fKm29WSL2ntHeRefHBQ==:" --signature-input "sig1=(\"@authority\");created=1770395703;keyid=\"FGzgs758DBGnI1S0BejChDsK0IKZm3qPpOOXdRnnBkM\";alg=\"ed25519\";expires=1770396003;nonce=\"O8JOC1reBofwbpPcdD-MRRCdrtAf4khvJTuhpRI_RiaH_hpU93okLkmPZVFFcUEdYtYfcduaB8Sca54GTd2GXA\";tag=\"web-bot-auth\"" --signature-agent "sig1=agent-609a4fd2ebf4e6347108c517.agents.vestauth.com"
|
|
338
|
+
{"uid":"agent-609a4fd2ebf4e6347108c517", ...}
|
|
257
339
|
```
|
|
258
340
|
|
|
259
341
|
</details>
|
|
260
342
|
|
|
261
343
|
|
|
262
344
|
|
|
263
|
-
##
|
|
345
|
+
## Compare
|
|
264
346
|
|
|
265
|
-
|
|
347
|
+
**Agent + Provider Matrix** – Compare Vestauth vs existing auth.
|
|
266
348
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
349
|
+
| Capability | Vestauth | API Keys | OAuth | Cookies |
|
|
350
|
+
|---|---|---|---|---|
|
|
351
|
+
| **Agent: no browser required** | ✅ | ✅ | ⚠️ (depends on flow) | ❌ |
|
|
352
|
+
| **Agent: easy to automate** | ✅ | ✅ | ⚠️ | ❌ |
|
|
353
|
+
| **Agent: no shared secret** | ✅ | ❌ | ⚠️ (bearer tokens) | ❌ |
|
|
354
|
+
| **Agent: per‑request identity proof** | ✅ | ❌ | ⚠️ (token‑based) | ❌ |
|
|
355
|
+
| **Agent: easy key/token rotation** | ✅ | ⚠️ | ⚠️ | ⚠️ |
|
|
356
|
+
| **Provider: no secret storage** | ✅ (public keys only) | ❌ | ❌ | ❌ |
|
|
357
|
+
| **Provider: strong attribution to agent** | ✅ | ⚠️ | ⚠️ | ❌ |
|
|
358
|
+
| **Provider: stateless verification** | ✅ | ✅ | ✅ | ❌ |
|
|
359
|
+
| **Provider: simple to implement** | ⚠️ (sig verification) | ✅ | ❌ | ✅ |
|
|
360
|
+
| **Provider: revocation control** | ✅ | ⚠️ | ✅ | ⚠️ |
|
|
361
|
+
|
|
362
|
+
Legend: ✅ strong fit, ⚠️ partial/conditional, ❌ poor fit
|
|
363
|
+
|
|
364
|
+
#### How It Works
|
|
365
|
+
|
|
366
|
+
1. An agent generates a public/private keypair.
|
|
367
|
+
2. The agent signs each HTTP request with its private key.
|
|
368
|
+
3. The provider verifies the signature using the agent’s public key.
|
|
369
|
+
4. Requests are attributable, auditable, and do not require shared secrets or browser sessions.
|
|
272
370
|
|
|
273
371
|
|
|
274
372
|
|
|
275
|
-
|
|
373
|
+
## Standards
|
|
276
374
|
|
|
277
|
-
|
|
375
|
+
Vestauth builds on open internet standards for agent authentication.
|
|
278
376
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
> Providers verify the request by retrieving the agent's public key from a discovery endpoint and verifying the signature cryptographically.
|
|
286
|
-
>
|
|
287
|
-
> If the signature is valid, the provider knows the request was created by the agent that owns that private key.
|
|
377
|
+
| Specification | Purpose |
|
|
378
|
+
|------------|------------|
|
|
379
|
+
| **[RFC 9421 – HTTP Message Signatures](https://datatracker.ietf.org/doc/rfc9421/)** | Defines how requests are cryptographically signed and verified |
|
|
380
|
+
| **[Web-Bot-Auth Draft](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture)** | Defines headers and authentication architecture for autonomous agents |
|
|
381
|
+
|
|
382
|
+
Vestauth follows these specifications to ensure interoperability between agents and providers while avoiding vendor lock-in. Vestauth focuses on developer ergonomics while staying compliant with these emerging standards.
|
|
288
383
|
|
|
289
384
|
|
|
290
385
|
|
|
291
|
-
</details>
|
|
292
386
|
|
|
293
|
-
<details><summary>Do I need to run a Vestauth server?</summary><br>
|
|
294
387
|
|
|
295
|
-
|
|
388
|
+
## FAQ
|
|
389
|
+
|
|
390
|
+
<details><summary>What problem does Vestauth solve?</summary><br>
|
|
391
|
+
|
|
392
|
+
> Vestauth gives agents a cryptographic identity and a simple way to authenticate HTTP requests.
|
|
296
393
|
>
|
|
297
|
-
>
|
|
394
|
+
> Most agent systems rely on API keys, bearer tokens, or username/passwords. These approaches are difficult to rotate, easy to leak, and hard to attribute to a specific agent.
|
|
298
395
|
>
|
|
299
|
-
>
|
|
396
|
+
> Vestauth replaces shared secrets with public/private key cryptography. Agents sign requests using a private key, and providers verify those requests using the agent's public key.
|
|
300
397
|
|
|
301
398
|
|
|
302
399
|
|
|
303
400
|
</details>
|
|
304
401
|
|
|
305
|
-
<details><summary>
|
|
402
|
+
<details><summary>Why not just use API keys?</summary><br>
|
|
306
403
|
|
|
307
|
-
>
|
|
308
|
-
>
|
|
309
|
-
> * `AGENT_PRIVATE_JWK` is used to sign requests and must never be shared.
|
|
310
|
-
> * `AGENT_PUBLIC_JWK` is safe to publish and is used by providers for verification.
|
|
404
|
+
> API keys are shared secrets. Anyone who obtains the key can impersonate the client, and keys are difficult to rotate safely.
|
|
311
405
|
>
|
|
312
|
-
> Vestauth
|
|
406
|
+
> Vestauth uses cryptographic signing instead of shared secrets. This allows providers to verify identity without storing or distributing sensitive credentials.
|
|
313
407
|
|
|
314
408
|
|
|
315
409
|
|
|
316
410
|
</details>
|
|
317
411
|
|
|
318
|
-
<details><summary>
|
|
412
|
+
<details><summary>Where are agent keys stored?</summary><br>
|
|
319
413
|
|
|
320
|
-
>
|
|
321
|
-
>
|
|
322
|
-
> Vestauth relies on asymmetric cryptography. Only the holder of the private key can generate valid signatures. Providers verify those signatures using the corresponding public key.
|
|
414
|
+
> Agent keys are generated locally and stored in the agent's environment configuration (`.env`).
|
|
323
415
|
>
|
|
324
|
-
>
|
|
416
|
+
> * `AGENT_PRIVATE_JWK` is used to sign requests and must never be shared.
|
|
417
|
+
> * `AGENT_PUBLIC_JWK` is safe to publish and is used by providers for verification.
|
|
325
418
|
|
|
326
419
|
|
|
327
420
|
|
|
328
421
|
</details>
|
|
329
422
|
|
|
330
|
-
<details><summary>
|
|
423
|
+
<details><summary>Is Vestauth only for AI agents?</summary><br>
|
|
331
424
|
|
|
332
|
-
>
|
|
425
|
+
> No.
|
|
333
426
|
>
|
|
334
|
-
>
|
|
427
|
+
> Vestauth can authenticate any automated system including:
|
|
428
|
+
>
|
|
429
|
+
> * developer tools
|
|
430
|
+
> * CLIs
|
|
431
|
+
> * automation services
|
|
432
|
+
> * bots
|
|
433
|
+
> * infrastructure tools
|
|
335
434
|
|
|
336
435
|
|
|
337
436
|
|
|
338
437
|
</details>
|
|
339
438
|
|
|
340
|
-
<details><summary>
|
|
439
|
+
<details><summary>Can Vestauth work without curl?</summary><br>
|
|
341
440
|
|
|
342
|
-
>
|
|
441
|
+
> Yes.
|
|
343
442
|
>
|
|
344
|
-
> Vestauth
|
|
443
|
+
> Vestauth provides libraries and primitives that can be integrated into any HTTP client or framework. The CLI simply makes it easy to adopt and demonstrate.
|
|
345
444
|
|
|
346
445
|
|
|
347
446
|
|
|
348
447
|
</details>
|
|
349
448
|
|
|
350
|
-
<details><summary>
|
|
449
|
+
<details><summary>Do I need to run a Vestauth server?</summary><br>
|
|
351
450
|
|
|
352
|
-
>
|
|
353
|
-
>
|
|
354
|
-
> Vestauth is built on established cryptographic and HTTP standards:
|
|
451
|
+
> No.
|
|
355
452
|
>
|
|
356
|
-
>
|
|
357
|
-
> * JOSE / JWK key formats
|
|
358
|
-
> * Web-Bot-Auth draft architecture
|
|
453
|
+
> Vestauth is primarily a client-side and verification library. Agents generate keys locally and sign requests directly. Providers verify requests using public keys exposed via .well-known discovery endpoints.
|
|
359
454
|
>
|
|
360
|
-
>
|
|
455
|
+
> There is no central authentication server required.
|
|
361
456
|
|
|
362
457
|
|
|
363
458
|
|
|
@@ -376,37 +471,17 @@ $ vestauth primitives verify GET https://example.com --signature "sig1=:K7z3Nozc
|
|
|
376
471
|
|
|
377
472
|
</details>
|
|
378
473
|
|
|
379
|
-
<details><summary>
|
|
380
|
-
|
|
381
|
-
> Yes.
|
|
382
|
-
>
|
|
383
|
-
> Vestauth provides libraries and primitives that can be integrated into any HTTP client or framework. The CLI simply makes it easy to adopt and demonstrate.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
</details>
|
|
388
|
-
|
|
389
|
-
<details><summary>Is Vestauth only for AI agents?</summary><br>
|
|
474
|
+
<details><summary>How does Vestauth authentication work?</summary><br>
|
|
390
475
|
|
|
391
|
-
>
|
|
476
|
+
> Vestauth uses HTTP Message Signatures ([RFC 9421](https://datatracker.ietf.org/doc/rfc9421/)). Each request is signed using the agent's private key. The request includes signed headers such as:
|
|
392
477
|
>
|
|
393
|
-
>
|
|
478
|
+
> * Signature
|
|
479
|
+
> * Signature-Input
|
|
480
|
+
> * Signature-Agent
|
|
394
481
|
>
|
|
395
|
-
>
|
|
396
|
-
> * CLIs
|
|
397
|
-
> * automation services
|
|
398
|
-
> * bots
|
|
399
|
-
> * infrastructure tools
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
</details>
|
|
404
|
-
|
|
405
|
-
<details><summary>Why not just use API keys?</summary><br>
|
|
406
|
-
|
|
407
|
-
> API keys are shared secrets. Anyone who obtains the key can impersonate the client, and keys are difficult to rotate safely.
|
|
482
|
+
> Providers verify the request by retrieving the agent's public key from a discovery endpoint and verifying the signature cryptographically.
|
|
408
483
|
>
|
|
409
|
-
>
|
|
484
|
+
> If the signature is valid, the provider knows the request was created by the agent that owns that private key.
|
|
410
485
|
|
|
411
486
|
|
|
412
487
|
|
|
@@ -436,6 +511,26 @@ $ vestauth primitives verify GET https://example.com --signature "sig1=:K7z3Nozc
|
|
|
436
511
|
|
|
437
512
|
</details>
|
|
438
513
|
|
|
514
|
+
<details><summary>Why does Vestauth use public key discovery?</summary><br>
|
|
515
|
+
|
|
516
|
+
> Public key discovery allows providers to verify agent signatures without manual key exchange. Each agent hosts its public keys in a standardized .well-known directory.
|
|
517
|
+
>
|
|
518
|
+
> This enables dynamic agent onboarding while preserving cryptographic verification.
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
</details>
|
|
523
|
+
|
|
524
|
+
<details><summary>Does Vestauth send secrets over the network?</summary><br>
|
|
525
|
+
|
|
526
|
+
> No.
|
|
527
|
+
>
|
|
528
|
+
> Vestauth signs requests using private keys locally. Only public keys are shared for verification.
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
</details>
|
|
533
|
+
|
|
439
534
|
<details><summary>How does Vestauth avoid SSRF during public key discovery?</summary><br>
|
|
440
535
|
|
|
441
536
|
> Vestauth prevents Server-Side Request Forgery (SSRF) by restricting public key discovery to trusted domains.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vestauth",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "auth for agents–from the creator of dotenvx",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vestauth"
|
|
@@ -38,14 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@dotenvx/dotenvx": "^1.52.0",
|
|
41
|
-
"@noble/hashes": "^1.8.0",
|
|
42
|
-
"@noble/secp256k1": "^1.7.2",
|
|
43
41
|
"commander": "^11.1.0",
|
|
44
|
-
"eciesjs": "^0.4.16",
|
|
45
42
|
"execa": "^5.1.1",
|
|
46
43
|
"structured-headers": "^2.0.2",
|
|
47
|
-
"undici": "7.11.0"
|
|
48
|
-
"web-bot-auth": "^0.1.2"
|
|
44
|
+
"undici": "7.11.0"
|
|
49
45
|
},
|
|
50
46
|
"devDependencies": {
|
|
51
47
|
"@yao-pkg/pkg": "^5.14.2",
|
|
@@ -55,6 +51,7 @@
|
|
|
55
51
|
"sinon": "^14.0.1",
|
|
56
52
|
"standard": "^17.1.0",
|
|
57
53
|
"standard-version": "^9.5.0",
|
|
58
|
-
"tap": "^21.0.1"
|
|
54
|
+
"tap": "^21.0.1",
|
|
55
|
+
"web-bot-auth": "^0.1.2"
|
|
59
56
|
}
|
|
60
57
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { logger } = require('./../../../shared/logger')
|
|
2
2
|
const agent = require('./../../../lib/agent')
|
|
3
3
|
const execute = require('./../../../lib/helpers/execute')
|
|
4
|
+
const Errors = require('./../../../lib/helpers/errors')
|
|
4
5
|
const findUrl = require('./../../../lib/helpers/findUrl')
|
|
5
6
|
const catchAndLog = require('./../../../lib/helpers/catchAndLog')
|
|
6
7
|
|
|
@@ -27,7 +28,7 @@ async function curl () {
|
|
|
27
28
|
|
|
28
29
|
if (exitCode !== 0) {
|
|
29
30
|
logger.debug(`received exitCode ${exitCode}`)
|
|
30
|
-
throw new
|
|
31
|
+
throw new Errors({ exitCode }).commandFailed()
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
let space = 0
|
|
@@ -28,7 +28,6 @@ async function verify (httpMethod, uri) {
|
|
|
28
28
|
const publicJwk = options.publicJwk ? JSON.parse(options.publicJwk) : undefined
|
|
29
29
|
|
|
30
30
|
const output = await primitives.verify(httpMethod, uri, headers, publicJwk)
|
|
31
|
-
// const output = await primitive.verifyWebBotAuth(httpMethod, uri, signature, signatureInput, JSON.parse(publicJwk))
|
|
32
31
|
|
|
33
32
|
let space = 0
|
|
34
33
|
if (options.prettyPrint) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
class Errors {
|
|
2
2
|
constructor (options = {}) {
|
|
3
3
|
this.message = options.message
|
|
4
|
+
this.exitCode = options.exitCode
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
missingId () {
|
|
@@ -107,6 +108,15 @@ class Errors {
|
|
|
107
108
|
return e
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
commandFailed () {
|
|
112
|
+
const code = 'COMMAND_FAILED'
|
|
113
|
+
const message = `[${code}] command failed with exit code ${this.exitCode}`
|
|
114
|
+
|
|
115
|
+
const e = new Error(message)
|
|
116
|
+
e.code = code
|
|
117
|
+
return e
|
|
118
|
+
}
|
|
119
|
+
|
|
110
120
|
econnrefused () {
|
|
111
121
|
const code = 'ECONNREFUSED'
|
|
112
122
|
const message = `[${code}] connection refused`
|
|
@@ -19,18 +19,6 @@ async function headers (httpMethod, uri, id, privateJwk, tag = 'web-bot-auth', n
|
|
|
19
19
|
const kid = thumbprint(privateJwk)
|
|
20
20
|
privateJwk.kid = kid
|
|
21
21
|
|
|
22
|
-
// // theirs
|
|
23
|
-
// const request = new Request(uri)
|
|
24
|
-
// const now = new Date()
|
|
25
|
-
// return await signatureHeaders(
|
|
26
|
-
// request,
|
|
27
|
-
// await signerFromJWK(JSON.parse(privateJwk)),
|
|
28
|
-
// {
|
|
29
|
-
// created: now,
|
|
30
|
-
// expires: new Date(now.getTime() + 300_000), // now + 5 min
|
|
31
|
-
// }
|
|
32
|
-
// )
|
|
33
|
-
|
|
34
22
|
const signatureInput = signatureParams(privateJwk.kid, tag, nonce)
|
|
35
23
|
const signature = webBotAuthSignature(httpMethod, uri, signatureInput, privateJwk)
|
|
36
24
|
const signatureAgent = `${id}.agents.vestauth.com` // agent-1234.agents.vestauth.com (no scheme) /.well-known/http-message-signatures-directory
|
package/src/lib/primitives.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
const keypair = require('./helpers/keypair')
|
|
2
2
|
const headers = require('./helpers/headers')
|
|
3
3
|
const verify = require('./helpers/verify')
|
|
4
|
-
const verifyWebBotAuth = require('./helpers/verifyWebBotAuth')
|
|
5
4
|
|
|
6
5
|
module.exports = {
|
|
7
6
|
keypair,
|
|
8
7
|
headers,
|
|
9
|
-
verify
|
|
10
|
-
verifyWebBotAuth
|
|
8
|
+
verify
|
|
11
9
|
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const which = require('which')
|
|
3
|
-
const execute = require('./../../lib/helpers/execute')
|
|
4
|
-
const { logger } = require('./../../shared/logger')
|
|
5
|
-
|
|
6
|
-
async function executeCommand (commandArgs) {
|
|
7
|
-
const signals = [
|
|
8
|
-
'SIGHUP', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
|
|
9
|
-
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2'
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
logger.debug(`executing process command [${commandArgs.join(' ')}]`)
|
|
13
|
-
|
|
14
|
-
let child
|
|
15
|
-
let signalSent
|
|
16
|
-
|
|
17
|
-
/* c8 ignore start */
|
|
18
|
-
const sigintHandler = () => {
|
|
19
|
-
logger.debug('received SIGINT')
|
|
20
|
-
logger.debug('checking command process')
|
|
21
|
-
logger.debug(child)
|
|
22
|
-
|
|
23
|
-
if (child) {
|
|
24
|
-
logger.debug('sending SIGINT to command process')
|
|
25
|
-
signalSent = 'SIGINT'
|
|
26
|
-
child.kill('SIGINT') // Send SIGINT to the command process
|
|
27
|
-
} else {
|
|
28
|
-
logger.debug('no command process to send SIGINT to')
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const sigtermHandler = () => {
|
|
33
|
-
logger.debug('received SIGTERM')
|
|
34
|
-
logger.debug('checking command process')
|
|
35
|
-
logger.debug(child)
|
|
36
|
-
|
|
37
|
-
if (child) {
|
|
38
|
-
logger.debug('sending SIGTERM to command process')
|
|
39
|
-
signalSent = 'SIGTERM'
|
|
40
|
-
child.kill('SIGTERM') // Send SIGTERM to the command process
|
|
41
|
-
} else {
|
|
42
|
-
logger.debug('no command process to send SIGTERM to')
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const handleOtherSignal = (signal) => {
|
|
47
|
-
logger.debug(`received ${signal}`)
|
|
48
|
-
child.kill(signal)
|
|
49
|
-
}
|
|
50
|
-
/* c8 ignore stop */
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
// ensure the first command is expanded
|
|
54
|
-
try {
|
|
55
|
-
commandArgs[0] = path.resolve(which.sync(`${commandArgs[0]}`))
|
|
56
|
-
logger.debug(`expanding process command to [${commandArgs.join(' ')}]`)
|
|
57
|
-
} catch (e) {
|
|
58
|
-
logger.debug(`could not expand process command. using [${commandArgs.join(' ')}]`)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// expand any other commands that follow a --
|
|
62
|
-
let expandNext = false
|
|
63
|
-
for (let i = 0; i < commandArgs.length; i++) {
|
|
64
|
-
if (commandArgs[i] === '--') {
|
|
65
|
-
expandNext = true
|
|
66
|
-
} else if (expandNext) {
|
|
67
|
-
try {
|
|
68
|
-
commandArgs[i] = path.resolve(which.sync(`${commandArgs[i]}`))
|
|
69
|
-
logger.debug(`expanding process command to [${commandArgs.join(' ')}]`)
|
|
70
|
-
} catch (e) {
|
|
71
|
-
logger.debug(`could not expand process command. using [${commandArgs.join(' ')}]`)
|
|
72
|
-
}
|
|
73
|
-
expandNext = false
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
child = execute.execa(commandArgs[0], commandArgs.slice(1), {
|
|
78
|
-
stdio: 'inherit'
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
process.on('SIGINT', sigintHandler)
|
|
82
|
-
process.on('SIGTERM', sigtermHandler)
|
|
83
|
-
|
|
84
|
-
signals.forEach(signal => {
|
|
85
|
-
process.on(signal, () => handleOtherSignal(signal))
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// Wait for the command process to finish
|
|
89
|
-
const { exitCode } = await child
|
|
90
|
-
|
|
91
|
-
if (exitCode !== 0) {
|
|
92
|
-
logger.debug(`received exitCode ${exitCode}`)
|
|
93
|
-
throw new Error(`Command exited with exit code ${exitCode}`)
|
|
94
|
-
}
|
|
95
|
-
} catch (error) {
|
|
96
|
-
// no color on these errors as they can be standard errors for things like jest exiting with exitCode 1 for a single failed test.
|
|
97
|
-
if (!['SIGINT', 'SIGTERM'].includes(signalSent || error.signal)) {
|
|
98
|
-
if (error.code === 'ENOENT') {
|
|
99
|
-
logger.error(`Unknown command: ${error.command}`)
|
|
100
|
-
} else {
|
|
101
|
-
logger.error(error.message)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Exit with the error code from the command process, or 1 if unavailable
|
|
106
|
-
process.exit(error.exitCode || 1)
|
|
107
|
-
} finally {
|
|
108
|
-
// Clean up: Remove the SIGINT handler
|
|
109
|
-
process.removeListener('SIGINT', sigintHandler)
|
|
110
|
-
// Clean up: Remove the SIGTERM handler
|
|
111
|
-
process.removeListener('SIGTERM', sigtermHandler)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = executeCommand
|
package/src/lib/helpers/hash.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
const { verify } = require('web-bot-auth')
|
|
2
|
-
const { verifierFromJWK } = require('web-bot-auth/crypto')
|
|
3
|
-
|
|
4
|
-
async function verifyWebBotAuth (httpMethod, uri, signatureHeader, signatureInputHeader, publicJwk) {
|
|
5
|
-
let success = false
|
|
6
|
-
|
|
7
|
-
const verifier = await verifierFromJWK(publicJwk)
|
|
8
|
-
const signedRequest = new Request(uri, {
|
|
9
|
-
headers: {
|
|
10
|
-
Signature: signatureHeader,
|
|
11
|
-
'Signature-Input': signatureInputHeader
|
|
12
|
-
}
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
await verify(signedRequest, verifier)
|
|
17
|
-
success = true
|
|
18
|
-
} catch (_e) {}
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
success
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
module.exports = verifyWebBotAuth
|