vestauth 0.7.0 β†’ 0.7.2

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 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.7.0...main)
5
+ [Unreleased](https://github.com/vestauth/vestauth/compare/v0.7.2...main)
6
+
7
+ ## [0.7.2](https://github.com/vestauth/vestauth/compare/v0.7.1...v0.7.2) (2026-02-04)
8
+
9
+ ### Changed
10
+
11
+ * Handle headers in a more flexible way
12
+
13
+ ## [0.7.1](https://github.com/vestauth/vestauth/compare/v0.7.0...v0.7.1) (2026-02-04)
14
+
15
+ ### Changed
16
+
17
+ * Raise an error if a bad `Signature-Agent` header ([#11](https://github.com/vestauth/vestauth/pull/11))
6
18
 
7
19
  ## [0.7.0](https://github.com/vestauth/vestauth/compare/v0.6.0...v0.7.0) (2026-02-03)
8
20
 
package/README.md CHANGED
@@ -2,67 +2,137 @@
2
2
 
3
3
  *auth for agents*–from the creator of [`dotenvx`](https://github.com/dotenvx/dotenvx).
4
4
 
5
- * identity
5
+ * identity (cryptographic)
6
6
  * authentication
7
+ * verification
7
8
 
8
9
   
9
10
 
10
11
  ### Quickstart [![npm version](https://img.shields.io/npm/v/vestauth.svg)](https://www.npmjs.com/package/vestauth) [![downloads](https://img.shields.io/npm/dw/vestauth)](https://www.npmjs.com/package/vestauth)
11
12
 
12
13
  ```sh
13
- curl -sfS https://vestauth.sh | sh
14
+ npm i -g vestauth
14
15
  ```
15
16
 
16
17
  ```sh
17
18
  vestauth agent init
18
- vestauth agent curl https://api.vestauth.com/whoami
19
19
  ```
20
20
 
21
21
   
22
22
 
23
- or install as npm - *unlocks vestauth inside code!*
23
+ or install globally - *unlocks vestauth for any agent, agent tool, or agent framework!*
24
24
 
25
- <details><summary>with npm πŸ“¦</summary><br>
25
+ <details><summary>with curl 🌐 </summary><br>
26
26
 
27
27
  ```sh
28
- npm install vestauth --save
28
+ curl -sfS https://vestauth.sh | sh
29
+ vestauth help
29
30
  ```
30
31
 
32
+ [![curl installs](https://img.shields.io/endpoint?url=https://vestauth.sh/stats/curl&label=curl%20installs)](https://github.com/vestauth/vestauth.sh/blob/main/install.sh)
33
+
31
34
  &nbsp;
32
35
 
33
36
  </details>
34
37
 
35
- ## Agent
38
+ <details><summary>with github releases πŸ™</summary><br>
39
+
40
+ ```sh
41
+ curl -L -o vestauth.tar.gz "https://github.com/vestauth/vestauth/releases/latest/download/vestauth-$(uname -s)-$(uname -m).tar.gz"
42
+ tar -xzf vestauth.tar.gz
43
+ ./vestauth help
44
+ ```
45
+
46
+ [![github releases](https://img.shields.io/github/downloads/vestauth/vestauth/total)](https://github.com/vestauth/vestauth/releases)
47
+
48
+ &nbsp;
49
+
50
+ </details>
36
51
 
37
- > Initialize your agent and make authenticated curl calls to vestauth providers.
52
+ <details><summary>or windows πŸͺŸ</summary><br>
53
+
54
+ Download [the windows executable](https://github.com/vestauth/vestauth/releases) directly from the [releases page](https://github.com/vestauth/vestauth/releases).
55
+
56
+ > * [vestauth-windows-amd64.zip
57
+ ](https://github.com/vestauth/releases/raw/main/latest/vestauth-windows-amd64.zip)
58
+ > * [vestauth-windows-x86_64.zip
59
+ ](https://github.com/vestauth/releases/raw/main/latest/vestauth-windows-x86_64.zip)
60
+
61
+ (unzip to extract `vestauth.exe`)
62
+
63
+ </details>
64
+
65
+ &nbsp;
66
+
67
+ ## Identity
68
+
69
+ > Give your agent its cryptographic identity.
38
70
 
39
71
  ```sh
72
+ $ mkdir your-agent
73
+ $ cd your-agent
74
+
40
75
  $ vestauth agent init
41
- $ vestauth agent curl https://ping.vestauth.com/ping
76
+ βœ” agent created (.env/AGENT_ID=agent-4b94ccd425e939fac5016b6b)
42
77
  ```
43
78
 
44
- More examples
79
+ <details><summary>learn more</summary><br>
45
80
 
46
- *coming soon*
81
+ This populates a `.env` file with an `AGENT_PUBLIC_JWK`, `AGENT_PRIVATE_JWK`, and `AGENT_ID`.
47
82
 
48
- ## Provider
83
+ ```ini
84
+ # example
85
+ AGENT_PUBLIC_JWK="{"crv":"Ed25519","x":"py2xNaAfjKZiau-jtmJls6h_3n8xJ1Ur0ie-n9b8zWg","kty":"OKP","kid":"B0u80Gw28W9U2Jl5t_EBiWeBajO2104kOYZ9Ikucl5I"}"
86
+ AGENT_PRIVATE_JWK="{"crv":"Ed25519","d":"Z9vbwN-3eiFMVv_TPWXOxqSMJAT21kZvejWi72yiAaQ","x":"py2xNaAfjKZiau-jtmJls6h_3n8xJ1Ur0ie-n9b8zWg","kty":"OKP","kid":"B0u80Gw28W9U2Jl5t_EBiWeBajO2104kOYZ9Ikucl5I"}"
87
+ AGENT_ID="agent-4b94ccd425e939fac5016b6b"
88
+ ```
49
89
 
50
- > As a provider of agentic tools, authenticate agents through cryptographic verification.
90
+ * The `AGENT_PUBLIC_KEY` is auto-hosted to its own [`/.well-known/http-message-signatures-directory`](https://datatracker.ietf.org/doc/html/draft-meunier-http-message-signatures-directory-04#appendix-A) for discovery purposes.
91
+ * The `AGENT_PRIVATE_KEY` must NOT be shared and is used to sign requests according to [RFC 9421](https://datatracker.ietf.org/doc/rfc9421/).
92
+ * The `AGENT_ID` contributes to building the [FQDN for the `Signature-Agent` header](https://datatracker.ietf.org/doc/html/draft-meunier-http-message-signatures-directory-01#name-request-with-http-signature).
93
+
94
+ </details>
95
+
96
+ &nbsp;
97
+
98
+ ## Authentication
99
+
100
+ > Turn any curl request into a signed, authenticated request.
51
101
 
52
102
  ```sh
53
- $ vestauth provider verify GET https://ping.vestauth.com/ping
103
+ > without vestauth
104
+ $ curl https://api.vestauth.com/whoami
105
+ {"error":{"status":400,"code":null,"message":"bad_request","help":null,"meta":null}}
106
+
107
+ > with vestauth
108
+ $ vestauth agent curl https://api.vestauth.com/whoami
109
+ {"uid":"agent-4b94ccd425e939fac5016b6b",...}
54
110
  ```
55
111
 
56
- More examples
112
+ <details><summary>learn more</summary><br>
57
113
 
58
- * Express.js
59
- * Next.js
60
- * Rails
61
- * ...
114
+ Vestauth autosigns each curl request – injecting valid signed headers according to the [web-bot-auth draft](https://datatracker.ietf.org/doc/html/draft-meunier-web-bot-auth-architecture). View these with the built-in `headers` primitive.
62
115
 
63
- ### List of current providers
116
+ ```
117
+ $ vestauth primitives headers GET https://api.vestauth.com/whoami --pp
118
+ {
119
+ "Signature": "sig1=:d4Id5SXhUExsf1XyruD8eBmlDtWzt/vezoCS+SKf0M8CxSkhKBtdHH7KkYyMN6E0hmxmNHsYus11u32nhvpWBQ==:",
120
+ "Signature-Input": "sig1=(\"@authority\");created=1770247189;keyid=\"B0u80Gw28W9U2Jl5t_EBiWeBajO2104kOYZ9Ikucl5I\";alg=\"ed25519\";expires=1770247489;nonce=\"NURxn28X7zyKJ9k5bHxuOyO5qdvF9L5s2qHmhTrGUzbwGSIoUCHmwSlwiiCRgTDGuum83yyWMHJU4jmrVI_XPg\";tag=\"web-bot-auth\"",
121
+ "Signature-Agent": "sig1=agent-4b94ccd425e939fac5016b6b.agents.vestauth.com"
122
+ }
123
+ ```
64
124
 
65
- * `dotenvx as2` – agentic secret storage
125
+ </details>
126
+
127
+ &nbsp;
128
+
129
+ ## Verification
130
+
131
+ > As a provider of agentic tools, authenticate agents through cryptographic verification.
132
+
133
+ ```sh
134
+
135
+ ```
66
136
 
67
137
  ## Advanced
68
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vestauth",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "auth for agents–from the creator of dotenvx",
5
5
  "keywords": [
6
6
  "vestauth"
@@ -2,37 +2,44 @@ const { logger } = require('./../../../shared/logger')
2
2
  const agent = require('./../../../lib/agent')
3
3
  const execute = require('./../../../lib/helpers/execute')
4
4
  const findUrl = require('./../../../lib/helpers/findUrl')
5
+ const catchAndLog = require('./../../../lib/helpers/catchAndLog')
5
6
 
6
7
  async function curl () {
7
- const commandArgs = this.args
8
- logger.debug(`process command [${commandArgs.join(' ')}]`)
9
-
10
- const options = this.opts()
11
- logger.debug(`options: ${JSON.stringify(options)}`)
12
-
13
- const httpMethod = 'GET'
14
- const url = findUrl(commandArgs)
15
- const headers = await agent.headers(httpMethod, url)
16
- const injected = [
17
- 'curl',
18
- '-H', `Signature: ${headers.Signature}`,
19
- '-H', `Signature-Input: ${headers['Signature-Input']}`,
20
- ...commandArgs
21
- ]
22
-
23
- const { stdout, exitCode } = await execute.execa(injected[0], injected.slice(1), {})
24
-
25
- if (exitCode !== 0) {
26
- logger.debug(`received exitCode ${exitCode}`)
27
- throw new Error(`Command exited with exit code ${exitCode}`)
8
+ try {
9
+ const commandArgs = this.args
10
+ logger.debug(`process command [${commandArgs.join(' ')}]`)
11
+
12
+ const options = this.opts()
13
+ logger.debug(`options: ${JSON.stringify(options)}`)
14
+
15
+ const httpMethod = 'GET'
16
+ const url = findUrl(commandArgs)
17
+ const headers = await agent.headers(httpMethod, url)
18
+ const injected = [
19
+ 'curl',
20
+ '-H', `Signature: ${headers.Signature}`,
21
+ '-H', `Signature-Input: ${headers['Signature-Input']}`,
22
+ '-H', `Signature-Agent: ${headers['Signature-Agent']}`,
23
+ ...commandArgs
24
+ ]
25
+
26
+ const { stdout, exitCode } = await execute.execa(injected[0], injected.slice(1), {})
27
+
28
+ if (exitCode !== 0) {
29
+ logger.debug(`received exitCode ${exitCode}`)
30
+ throw new Error(`Command exited with exit code ${exitCode}`)
31
+ }
32
+
33
+ let space = 0
34
+ if (options.prettyPrint) {
35
+ space = 2
36
+ }
37
+
38
+ console.log(JSON.stringify(JSON.parse(stdout), null, space))
39
+ } catch (error) {
40
+ catchAndLog(error)
41
+ process.exit(1)
28
42
  }
29
-
30
- let space = 0
31
- if (options.prettyPrint) {
32
- space = 2
33
- }
34
-
35
- console.log(JSON.stringify(JSON.parse(stdout), null, space))
36
43
  }
37
44
 
38
45
  module.exports = curl
@@ -2,6 +2,13 @@ function authorityMessage (uri, signatureParams) {
2
2
  const u = new URL(uri)
3
3
  const authority = u.host // includes port if present
4
4
 
5
+ // other possible message (target-uri, and signature-params)
6
+ // const message = [
7
+ // `"@method": ${method.toUpperCase()}`,
8
+ // `"@target-uri": ${uri}`,
9
+ // `"@signature-params": ${signatureParams}`
10
+ // ].join('\n')
11
+
5
12
  return [
6
13
  `"@authority": ${authority}`,
7
14
  `"@signature-params": ${signatureParams}`
@@ -36,6 +36,17 @@ class Errors {
36
36
  return e
37
37
  }
38
38
 
39
+ invalidSignatureAgent () {
40
+ const code = 'INVALID_SIGNATURE_AGENT'
41
+ const message = `[${code}] invalid --signature-agent`
42
+ const help = `[${code}] https://github.com/vestauth/vestauth/issues/11`
43
+
44
+ const e = new Error(message)
45
+ e.code = code
46
+ e.help = help
47
+ return e
48
+ }
49
+
39
50
  econnrefused () {
40
51
  const code = 'ECONNREFUSED'
41
52
  const message = `[${code}] connection refused`
@@ -33,7 +33,7 @@ async function headers (httpMethod, uri, id, privateJwk, tag = 'web-bot-auth', n
33
33
 
34
34
  const signatureInput = signatureParams(privateJwk.kid, tag, nonce)
35
35
  const signature = webBotAuthSignature(httpMethod, uri, signatureInput, privateJwk)
36
- const signatureAgent = `https://${id}.agents.vestauth.com` // https://agent-1234.agents.vestauth.com/.well-known/http-message-signatures-directory
36
+ const signatureAgent = `${id}.agents.vestauth.com` // agent-1234.agents.vestauth.com (no scheme) /.well-known/http-message-signatures-directory
37
37
 
38
38
  return {
39
39
  Signature: `sig1=:${signature}:`,
@@ -1,13 +1,14 @@
1
1
  const { parseDictionary } = require('structured-headers')
2
+ const Errors = require('./errors')
2
3
 
3
4
  // example: sig1=https://agent-9aa52a556ca85ee195866c0b.agents.vestauth.com
4
5
  function parseSignatureAgentHeader (signatureAgentHeader) {
5
6
  const dictionary = parseDictionary(signatureAgentHeader)
6
7
  const entry = dictionary.entries().next()
7
- if (entry.done) throw new Error('Invalid Signature-Agent header: empty dictionary')
8
+ if (entry.done) throw new Errors().invalidSignatureAgent()
8
9
  const [key, innerlist] = entry.value
9
10
  const value = innerlist[0].value
10
- if (!value) throw new Error('Invalid Signature-Agent header: expected a URL string')
11
+ if (!value) throw new Errors().invalidSignatureAgent()
11
12
 
12
13
  return {
13
14
  key,
@@ -1,13 +1,18 @@
1
1
  const { http } = require('./http')
2
2
  const buildApiError = require('./buildApiError')
3
3
  const parseSignatureAgentHeader = require('./parseSignatureAgentHeader')
4
+ const verifyAgentFqdn = require('./verifyAgentFqdn')
4
5
  const verify = require('./verify')
5
6
 
6
- async function providerVerify (httpMethod = 'GET', uri = 'https://api.vestauth.com/whoami', headers = {}) {
7
- const signatureAgent = headers['Signature-Agent']
8
- const { value } = parseSignatureAgentHeader(signatureAgent) // sig1=https://agent-9aa52a556ca85ee195866c0b.agents.vestauth.com
7
+ async function providerVerify (httpMethod, uri, headers = {}) {
8
+ const signatureAgent = headers['Signature-Agent'] || headers['signature-agent'] // support either case (expressjs lowers headers)
9
+ const { value } = parseSignatureAgentHeader(signatureAgent) // sig1=agent-1234.agents.vestauth.com
10
+ const fqdn = value
11
+ verifyAgentFqdn(fqdn)
12
+ const origin = `https://${fqdn}` // https://agent-1234.agents.vestauth.com
9
13
 
10
- const url = `${value}/.well-known/http-message-signatures-directory`
14
+ // get jwks from .well-known
15
+ const url = `${origin}/.well-known/http-message-signatures-directory` // risk of SSRF ?
11
16
  const resp = await http(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } })
12
17
  if (resp.statusCode >= 400) {
13
18
  const json = await resp.body.json()
@@ -26,6 +31,8 @@ async function providerVerify (httpMethod = 'GET', uri = 'https://api.vestauth.c
26
31
  // }
27
32
  // ]
28
33
  // }
34
+
35
+ // use publicJwk to verify
29
36
  const publicJwk = json.keys[0]
30
37
  const output = await verify(httpMethod, uri, headers, publicJwk)
31
38
 
@@ -6,8 +6,8 @@ const authorityMessage = require('./authorityMessage')
6
6
  const publicJwkObject = require('./publicJwkObject')
7
7
 
8
8
  function verify (httpMethod, uri, headers = {}, publicJwk) {
9
- const signature = headers.Signature
10
- const signatureInput = headers['Signature-Input']
9
+ const signature = headers.Signature || headers.signature
10
+ const signatureInput = headers['Signature-Input'] || headers['signature-input']
11
11
 
12
12
  const { values } = parseSignatureInputHeader(signatureInput)
13
13
  const { expires } = values
@@ -0,0 +1,28 @@
1
+ const DEFAULT_PROVIDER_FQDN_REGEX = /^[A-Za-z0-9-]+\.agents\.vestauth\.com$/
2
+ const Errors = require('./errors')
3
+
4
+ function getProviderFqdnRegex () {
5
+ const override = process.env.PROVIDER_FQDN_REGEX
6
+ if (!override) return DEFAULT_PROVIDER_FQDN_REGEX
7
+
8
+ try {
9
+ return new RegExp(override)
10
+ } catch {
11
+ return DEFAULT_PROVIDER_FQDN_REGEX
12
+ }
13
+ }
14
+
15
+ function verifyAgentFqdn (fqdn) {
16
+ if (!fqdn || typeof fqdn !== 'string') {
17
+ throw new Errors().invalidSignatureAgent()
18
+ }
19
+
20
+ const pattern = getProviderFqdnRegex()
21
+ if (!pattern.test(fqdn)) {
22
+ throw new Errors().invalidSignatureAgent()
23
+ }
24
+
25
+ return true
26
+ }
27
+
28
+ module.exports = verifyAgentFqdn
@@ -5,12 +5,6 @@ const authorityMessage = require('./authorityMessage')
5
5
  function webBotAuthSignature (method = 'GET', uri = '', signatureParams, privateJwk) {
6
6
  const message = authorityMessage(uri, signatureParams)
7
7
 
8
- // const message = [
9
- // `"@method": ${method.toUpperCase()}`,
10
- // `"@target-uri": ${uri}`,
11
- // `"@signature-params": ${signatureParams}`
12
- // ].join('\n')
13
-
14
8
  return crypto.sign(
15
9
  null,
16
10
  Buffer.from(message, 'utf8'),