Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Go CI
on:
push:
branches: [main, dev]
paths: ['go/**', '.github/workflows/go.yml']
pull_request:
paths: ['go/**', '.github/workflows/go.yml']

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.21', '1.22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: gofmt
working-directory: go
run: |
out=$(gofmt -l .)
if [ -n "$out" ]; then
echo "::error::gofmt would reformat the following files:"
echo "$out"
exit 1
fi
- name: go vet
working-directory: go
run: go vet ./...
- name: go test
working-directory: go
run: go test ./...
27 changes: 27 additions & 0 deletions .github/workflows/javascript.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: JavaScript CI
on:
push:
branches: [main]
paths: ['javascript/**']
pull_request:
paths: ['javascript/**']

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['18', '20']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- working-directory: javascript
# The JS SDK has no runtime deps and intentionally does not commit a
# lockfile (it is in .gitignore), so use `npm install` instead of
# `npm ci`. Lock-free installs are fine here since devDependencies are
# only used for tests/lint, not anything that ships.
run: |
npm install --no-audit --no-fund
npm test
23 changes: 23 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Python CI
on:
push:
branches: [main]
paths: ['python/**']
pull_request:
paths: ['python/**']

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.12']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- working-directory: python
run: |
pip install -e ".[dev]"
python -m pytest tests/ -v
26 changes: 26 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Release Check
on:
push:
branches: [main]
pull_request:

jobs:
version-consistency:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check version consistency
run: |
RUST_VER=$(grep '^version' crates/agentpin/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
PY_VER=$(grep 'version' python/pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
JS_VER=$(node -e "console.log(require('./javascript/package.json').version)")
GO_VER=$(grep -E '^const Version' go/internal/version/version.go | sed 's/.*"\(.*\)".*/\1/')
echo "Rust: $RUST_VER"
echo "Python: $PY_VER"
echo "JavaScript: $JS_VER"
echo "Go: $GO_VER"
if [ "$RUST_VER" != "$PY_VER" ] || [ "$RUST_VER" != "$JS_VER" ] || [ "$RUST_VER" != "$GO_VER" ]; then
echo "::error::Version mismatch! Rust=$RUST_VER Python=$PY_VER JavaScript=$JS_VER Go=$GO_VER"
exit 1
fi
echo "All versions match: $RUST_VER"
26 changes: 26 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Rust CI
on:
push:
branches: [main]
paths: ['crates/**', 'Cargo.toml', 'Cargo.lock']
pull_request:
paths: ['crates/**', 'Cargo.toml', 'Cargo.lock']

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [stable, '1.86']
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: clippy, rustfmt
- run: cargo fmt --check
if: matrix.rust == 'stable'
- run: cargo clippy --workspace -j2 -- -D warnings
if: matrix.rust == 'stable'
- run: cargo test --workspace -j2
- run: cargo test --workspace -j2 --features fetch
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Cargo.lock
# Private keys — NEVER commit these
*.private.pem
*.private.jwk.json
# Exception: cross-language interop test fixtures under testdata/ ARE
# deliberately committed throwaway keypairs used only for SDK interop tests.
# They are NOT used to sign anything in production.
!go/pkg/verification/testdata/*.private.pem

# IDE
.idea/
Expand Down
121 changes: 121 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,127 @@ All notable changes to the AgentPin project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.0] - 2026-05-14

### Added

Four-language parity (Rust, JavaScript, Python, Go) for the A2A AgentCard
extension surface and DNS TXT cross-verification. Cards signed in any of the
four SDKs verify cleanly in the other three; signature canonicalisation is
byte-identical across implementations.

#### A2A AgentCard extension types, signed builder, and verifier

- **`A2aAgentCard` + supporting types** — minimal A2A AgentCard subset
(`A2aAgentCard`, `A2aAgentCapabilities`, `A2aAgentSkill`) plus the
AgentPin-specific `AgentpinExtension` payload (`agentpin_endpoint`,
`public_key_jwk`, `signature`). Inline definition rather than depending on
the upstream `a2a-types` crate while the A2A spec is still draft.
- **`A2aAgentCardBuilder`** (Rust) / `buildAndSignAgentCard` (JS) /
`build_and_sign_agent_card` (Python) / `a2a.BuildAndSignAgentCard` (Go) —
turns an AgentDeclaration into a signed `A2aAgentCard`. Maps capabilities
to skills via `capability_to_skill`; propagates `allowed_domains` from the
source constraints into `A2aAgentCapabilities.allowed_domains`. Detached
ECDSA P-256 signature covers the canonical bytes of the AgentCard with the
extension cleared.
- **`verify_agentpin_extension(card)`** — verifies the AgentPin extension
signature against the JWK embedded in the extension. Sorted-key canonical
JSON shared across all four SDKs; mirrors the canonicalisation pattern
used by SchemaPin.
- **`AllowedDomains` typed wrapper** — extracted from
`Constraints::allowed_domains` and exposed for cross-protocol use
(SchemaPin v1.4's `A2aVerificationContext` scopes tool verification to the
intersection of caller and provider domains). Empty list = no restriction
(all domains trusted) by convention; `intersect(unrestricted, X) = X`.
- **`a2a_endpoint` field** on `DiscoveryDocument` — optional URL of the
entity's A2A AgentCard endpoint, enabling cross-protocol discovery.

#### Discovery resolvers for A2A AgentCards

- **`LocalAgentCardStore`** (all SDKs) — in-memory store of pre-registered
AgentCards keyed by their AgentPin discovery domain. Verifies the
AgentPin extension signature at registration time and pre-derives a
`DiscoveryDocument` so the rest of the AgentPin verification stack runs
unchanged. Supports Symbiont's push-based external-agent registration
flow where the coordinator receives AgentCard JSON inline rather than
fetching it from a `.well-known` endpoint.
- **`A2aAgentCardResolver`** (all SDKs; gated on `fetch` in Rust) — fetches
`https://{domain}/.well-known/agent-card.json`, verifies the AgentPin
extension, cross-checks that the embedded `agentpin_endpoint` host
matches the fetched domain, and derives a `DiscoveryDocument`. Exposes
the original A2A representation alongside the derived doc for callers
that want both.

#### DNS TXT cross-verification at `_agentpin.{domain}`

- **`dns` module** (all SDKs) — `parse_txt_record`, `verify_dns_match`,
`txt_record_name`. Wire format mirrors SchemaPin's `_schemapin.{domain}`
record with the version tag changed:
`"v=agentpin1; kid=...; fp=sha256:<hex>"`. Whitespace-tolerant parser,
case-insensitive on `fp`, ignores unknown fields for forward
compatibility.
- **`fetch_dns_txt(domain)`** — Rust: async lookup behind the new `dns`
Cargo feature (`hickory-resolver`, `tokio`, `async-trait`). JavaScript:
uses Node's built-in `dns/promises`. Python: uses the optional
`dnspython` package. Go: `dns.LookupTxt(ctx, resolver, domain)` over the
standard-library `net.Resolver`.
- **Multi-key match semantics** — AgentPin discovery docs may carry several
keys for rotation; a published TXT record need only match one of them.
When the TXT carries an explicit `kid`, the matching key MUST also carry
the same `kid`.
- **Fail-closed on mismatch** — a publisher who *intentionally* publishes a
TXT record has signaled DNS is part of their trust chain. Divergence
between DNS and `.well-known` indicates compromise of one channel and is
treated as a hard failure.

#### Go SDK (fourth-language port)

- **Initial `go/` SDK** — wire-compatible with Rust, JavaScript, and Python.
Mirrors the package layout of the SchemaPin Go SDK. Module path:
`github.com/ThirdKeyAi/agentpin/go`.
- **Packages**: `crypto`, `jwk`, `jwt`, `types`, `discovery`, `credential`,
`verification`, `revocation`, `pinning`, `delegation`, `mutual`, `nonce`,
`bundle`, `resolver`, plus the new `a2a` and `dns` packages added in this
release.
- **CLI**: `cmd/agentpin` with `keygen`, `issue`, `verify`, `bundle`
subcommands matching the Rust binary.
- **ES256-only** enforcement implemented inline using `crypto/ecdsa`. The
JWT verifier rejects `none`, `HS256`, `RS256`, `ES384`, and any other
algorithm before any signature work. No third-party JWT dependency.
- **CI**: `.github/workflows/go.yml` runs `go test`, `go vet`, and
`gofmt -l` on every PR touching `go/**`. The version-consistency check
in `.github/workflows/release.yml` validates the Go SDK's declared
version against the Rust/JavaScript/Python versions.

### Changed

- Cross-SDK version coordination — Rust, JavaScript, Python, and Go SDKs
all release as **0.3.0** together. The earlier `0.3.0-alpha.1` Rust
preview is superseded by this entry.
- **Rust MSRV bumped from 1.70 to 1.86.** Downstream ecosystem crates
(`getrandom`, `clap_builder`, the `icu_*` family) have moved to edition
2024 and/or to declared rust-version 1.86, making the previously-
declared 1.70 floor unbuildable from scratch in practice. The CI
matrix's MSRV row now tests against 1.86.

### Notes

- This release is the unblock for **Symbiont v1.8.0 Phase 3** (AgentPin-
verified AgentCards, A2A auth middleware) and **SchemaPin v1.4.0**'s
`A2aVerificationContext` (which consumes `AllowedDomains` for tool-
verification scoping). Both downstream releases were waiting on this
surface.
- DNS TXT defends against HTTPS-origin compromise (compromised hosting
account, expired domain not removed from CDN, ACME ownership-validation
bypass) and TLS cert mis-issuance — the DNS credential chain (registrar,
DNS provider, optionally DNSSEC) is independent of the HTTPS hosting
chain. Spec § 4.8.3 reserved this slot in v0.1; this release ships the
implementation across all SDKs.
- All additions are purely additive — v0.2.0 callers are unaffected.
Discovery documents without `a2a_endpoint`, AgentCards without an
`agentpin` extension, and absent `_agentpin` TXT records all behave
exactly as before.

## [0.2.0] - 2026-02-12

### Added
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ AgentPin lets organizations publish verifiable identity for their AI agents. Iss
- **Credential revocation** at credential, agent, and key level
- **Mutual authentication** with challenge-response
- **Trust bundles** for air-gapped and enterprise environments
- **Cross-language** — Rust, JavaScript, and Python SDKs produce interoperable credentials
- **Cross-language** — Rust, JavaScript, Python, and Go SDKs produce interoperable credentials

## Quick Start

Expand All @@ -43,7 +43,7 @@ agentpin verify --credential <jwt>

```toml
[dependencies]
agentpin = { version = "0.2", features = ["fetch"] }
agentpin = { version = "0.3", features = ["fetch"] }
```

### JavaScript
Expand All @@ -58,6 +58,13 @@ npm install agentpin
pip install agentpin
```

### Go

```bash
go install github.com/ThirdKeyAi/agentpin/go/cmd/agentpin@latest
go get github.com/ThirdKeyAi/agentpin/go
```

## Documentation

| Topic | Link |
Expand All @@ -80,6 +87,7 @@ crates/
└── agentpin-server/ # HTTP server for .well-known endpoints
javascript/ # JavaScript/Node.js SDK
python/ # Python SDK
go/ # Go SDK
```

## License
Expand Down
Loading
Loading