Skip to content

feat(wallets): add Azure Key Vault signer - #17120

Draft
aelmanaa wants to merge 1 commit into
foundry-rs:masterfrom
aelmanaa:feat/azure-key-vault-signer
Draft

aelmanaa wants to merge 1 commit into
foundry-rs:masterfrom
aelmanaa:feat/azure-key-vault-signer

Conversation

@aelmanaa

@aelmanaa aelmanaa commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Depends on foundry-rs/foundry-core#228 and the next alloy release. alloy-rs/alloy#4267 is merged, but alloy-signer-azure isn't on crates.io yet. Until both are released, alloy is patched from upstream git at the #4267 merge commit and foundry-core from my fork; the patches will be replaced by the releases before merging.

Closes #17112

Wires the azure-key-vault wallet feature from foundry-wallets through cast and forge and enables it in release builds, so --azure signs with an Azure Key Vault or Managed HSM P-256K key selected by AZURE_KEY_VAULT_KEY_ID, like --aws, --gcp, and --turnkey. It is also accepted by cast wallet list --azure, Tempo sessions, and --tempo.sponsor-signer azure://, conflicts with forge script --unlocked, and the key identifier variables are removed from cast wallet session child processes. The feature is added to the Makefile, release, and Docker feature lists together.

I tested a build end to end against a real Key Vault on Sepolia: cast send --azure, and forge script --azure --broadcast deploying and calling a contract, with both an Azure CLI login and a service principal. CLI tests cover the missing key identifier and both feature-on and feature-off builds. This depends on foundry-rs/foundry-core#228 and the release of alloy-rs/alloy#4267, which are patched from git until then.

This PR was written primarily with Claude Code; I reviewed it and ran the end-to-end test against a real Azure Key Vault.

@aelmanaa
aelmanaa force-pushed the feat/azure-key-vault-signer branch from 34fcb54 to e7a2b06 Compare September 28, 2026 16:12
@aelmanaa
aelmanaa marked this pull request as ready for review September 28, 2026 16:23
@mablr mablr added the T-blocked Type: blocked label Sep 29, 2026

@mablr mablr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, marking this blocked until foundry-rs/foundry-core#228 is reviewed.

@mablr mablr changed the title feat: add Azure Key Vault signer feat(wallets): add Azure Key Vault signer Sep 29, 2026
@mablr
mablr marked this pull request as draft September 29, 2026 12:54
aelmanaa added a commit to aelmanaa/hardhat-kms that referenced this pull request Oct 3, 2026
The --kms option help said every provider uses Foundry's variables.
aws and gcp do; azure uses the names proposed in
foundry-rs/foundry#17120, which no Foundry release includes. The Azure
credential and adapter comments now say the same.

Refs #221

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
aelmanaa added a commit to aelmanaa/hardhat-kms that referenced this pull request Oct 3, 2026
)

Closes #218
Closes #221

## What changes

**Ignition guide (#218).** `docs/user/guides/deploy-with-ignition.md`:

- Step 1's config adds `rehearsalCli: { type: "edr-simulated" }`, a
network with no KMS keys, and step 3's `--kms` command passes `--network
rehearsalCli`. The page says why it does not use `rehearsal`: that
network already lists `deployer`, and the same key passed twice is an
error (decision 0008). An earlier revision ran `--kms` without
`--network`; that works but prints the plugin's warning against KMS keys
on `default`, so review moved it to a named network.
- `configuration.md`: the default-network warning sentence now says it
also fires for `--kms` without `--network`.
- Step 1 shows `contracts/Counter.sol`, byte for byte the same as
`examples/ignition/contracts/Counter.sol`.
- Step 1's config adds a `sepoliaFork` network (`forking: { url:
configVariable("SEPOLIA_RPC_URL") }`), and step 3 shows the command that
rehearses on it. Step 2's account-index sentence now names both
simulated networks.

**Foundry's Azure signer (#221).** Foundry has no released Azure Key
Vault signer. Checked with `gh` on 2026-10-03:

- foundry-rs/foundry#17120: open, draft. foundry-rs/foundry-core#228:
open, draft.
- Latest release: v1.8.4 (2026-10-01). Its notes do not mention Azure.

The pages that said or implied a released `--azure` now say the Azure
variable names and credential order follow the proposal in #17120, and
link it: `migrate-from-foundry.md` (intro sentence plus a note under the
variables table), `azure-key-vault-setup.md` (the one Foundry sentence
in step 3), `configuration.md` (the `--kms` paragraph and the Azure
credential chain) and `multiple-keys.md` (the `--kms` intro). The `--kms
azure` behaviour is unchanged. Decision 0008 already qualifies the Azure
names as following the open PRs, so it is unchanged.

The same fix in code (second commit):

- The `--kms` option's help text now reads: "Load KMS keys from
environment variables for these providers: aws, gcp, azure
(comma-separated). aws and gcp use Foundry's variables; azure uses the
names proposed in foundry-rs/foundry#17120". `npx hardhat --help` shows
the new text. No test or doc quotes it. A `hardhat-kms` patch changeset
covers it.
- The `KmsOption` TSDoc in `packages/hardhat-kms/src/type-extensions.ts`
says the same. `pnpm run docs:api` regenerated no page, because the API
reference does not include this global-option augmentation.
- The comments in
`packages/hardhat-kms-azure/src/internal/credential.ts` (credential
order) now refer to the signer proposed in foundry-rs/foundry#17120. The
`adapter.ts` comment on the `kid` check now names alloy's Azure signer
(alloy-rs/alloy#4267, merged 2026-09-30), whose `sign` checks the
response's key version (`crates/signer-azure/src/signer.rs:316` at
`3834f7e`). Comment changes need no changeset.
- The two "another order than Foundry's" comparisons
(`hook-handlers/kms.ts:49`, `scripts/type-escapes.json`) are unchanged:
they compare orders and claim no release.

## Evidence

Run on 2026-10-03 from this branch: `pnpm run build`, then `pnpm pack`
of `hardhat-kms` and `@hardhat-kms/aws` into a new Hardhat 3 project
(hardhat 3.18.1, hardhat-ignition 3.1.8, hardhat-ignition-viem 3.1.6,
Node 24). The project's `hardhat.config.ts`, `contracts/Counter.sol` and
both Ignition modules were extracted from the guide's code blocks by a
script. The one change was `keyId: "alias/deployer"`, replaced with a
`configVariable` that names an existing AWS test key (a diff confirmed
that this was the only difference). `--kms` runs set `AWS_KMS_KEY_ID` to
the same key. Simulated networks only: no live broadcast, no funds.
Addresses masked.

| Command (guide step) | Result |
| --- | --- |
| `kms accounts --network rehearsal` | `deployer aws kms.keys 0x<addr>`
|
| `ignition deploy …Counter.ts --network rehearsal --default-sender
0x<addr>` (3) | exit 0, `[ Counter ] successfully deployed`,
`Counter.add` executed |
| `ignition deploy …Counter.ts --network sepoliaFork --default-sender
0x<addr>` (3), `SEPOLIA_RPC_URL` = a public keyless Sepolia endpoint |
exit 0, deployed, `add` executed; a script on `sepoliaFork` read block
11836750, so the fork reads Sepolia |
| `AWS_KMS_KEY_ID=… ignition deploy …Counter.ts --network rehearsalCli
--kms aws --default-sender 0x<addr>` (3, new) | exit 0, deployed, `add`
executed, no plugin warning |
| `ignition deploy …CounterFromKms.ts --network rehearsal` (2,
`m.getAccount(20)`) | exit 0, deployed |
| The old step 3 command, `--network rehearsal --kms aws` | exit 1, as
the page now says: `aws, load accounts: AWS_KMS_KEY_ID is already
networks.rehearsal.kmsAccounts[0] ("deployer"); use one of them` |

`Counter.add` requires `msg.sender == owner`, so each executed `add`
shows that the KMS account deployed the contract and sent the call. The
`sepolia` commands in steps 2 and 4, and the script in step 2, broadcast
to Sepolia, so they were not run.

CI already covers `--kms aws` on a network without `kmsAccounts`: the
examples test
(`packages/hardhat-kms-aws/test/examples/examples.test.ts`) runs
`ignition deploy --network localhost --kms aws --default-sender` against
LocalStack. No test change was needed.

Gates: `pnpm run docs:check` (53 pages, snippets typecheck), `pnpm run
check` and `pnpm test` (1,567 tests, 0 failures) pass. The prose slop
gate reports 0 findings.

## AI use

An AI coding agent (Claude Code) wrote the changes and ran the checks
above. A maintainer reviews the PR before merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
aelmanaa added a commit to aelmanaa/hardhat-kms that referenced this pull request Oct 3, 2026
Closes #224

User pages (`docs/user/**`, `README.md`, `packages/*/README.md`) no
longer carry milestone codes, maintainer notes in HTML comments, or
links that work only inside the repository.

## What changes

- **Opening lines.** Each page's opening now says what works and what
was checked, in terms a reader outside the project understands, without
a `Status:` prefix. Lines that only said "Implemented, on `main`" or
listed the issues that built a feature are gone. Caveats a user needs
stay, reworded:
- the AWS policy, the Google Cloud least-privilege setup and the Azure
custom role are not yet checked against real KMS (the Azure caveat is
new; the live tests ran with the developer's own identity);
  - the plugin is not on npm yet;
  - each tutorial gives the date and versions it was followed with.
- **Issue links.** The links that recorded which issue built a feature
are removed.
- **Contributor links.** Inline links to the signing pipeline, testing
and decision records now point at a user page (the security model) or
are dropped. The links that stay are in "Read next" lists or for
provider authors, and say "optional" and "in the contributor docs". Two
facts that lived only behind those links are now written into the user
pages:
- in `configuration.md`, how the plugin order with hardhat-ledger
changes `eth_accounts` and `eth_requestAccounts`, and why that matters
for index-based picks such as Ignition's `m.getAccount(index)`;
- in `debug-output.md`, that the providers and signer namespaces print
nothing until a connection first uses a key.
- **viem authorizations.** The viem `SignedAuthorization` row in the
`kms sign-auth` table said about one tuple in 128 is refused, and to use
`provider.request` until #140 is fixed. #142 fixed it for KMS senders.
`canonicalAuthorizationSignatures` in `transaction-filler.ts` accepts
`r` and `s` as quantities and pads them to 32 bytes before signing.
Tests send viem authorizations with a leading zero byte in `r` and in
`s`, in `transaction-filler.test.ts` (unit) and
`sign-transactions.test.ts` (integration). The row now says that. It
also says that from an account in the network's `accounts`, Hardhat
still refuses a short `r` or `s`: `rpcAuthorizationList` in
hardhat-zod-utils 3.0.6 types them as `rpcHash`, exactly 32 bytes. The
issue link is gone.
- **Template note.** The HTML comment at the top of
`first-deploy-aws.md` moves to the rules in
`docs/contributor/documentation.md`. That page's milestone rule for user
pages is replaced by the new rule.
- **Check.** `pnpm run docs:check` now runs a user-page rule, in
`scripts/user-pages.ts`, with unit tests in
`test/scripts/user-pages.test.ts` (12 cases, in `pnpm test`).
  - It fails on a milestone code or an HTML comment in a user page.
- Fenced, indented and inline code, URLs, link targets and Apple chip
names such as "M1 chip" do not count.
- Two comments are allowed by name: `<!-- docs-check: skip -->`, and the
"Pre-release only." note at the top of `install-before-release.md`,
which the release PR uses.

## Before and after

Occurrences across `docs/user/**`, `README.md` and
`packages/*/README.md`:

| Pattern                               | Before | After |
| ------------------------------------- | -----: | ----: |
| Milestone codes (`\bM\d+\b`)          |     15 |     0 |
| HTML comments                         |      3 |     2 |
| Links into `docs/contributor/`        |     30 |     7 |
| Links to this repository's issues     |     38 |     3 |
| Links to `docs/live-proof.md`         |      1 |     0 |

What remains, and why:

- **HTML comments (2).** The `docs-check: skip` marker in
`configuration.md` is a tooling directive. The pre-release note in
`install-before-release.md` is kept on purpose for the release PR.
- **Contributor links (7).** Each is labelled optional and in the
contributor docs:
  - `how-it-works.md` "Read next" (2);
  - `security-model.md` "Read next" (1) and its send-lock paragraph (2);
  - `configuration.md` provider contract (1);
  - `tasks.md` history readers (1).
- **Issue links (3).**
- `install-before-release.md` links #48 and #47. Readers of that page
need read access to the repository, so the links resolve for them, and
the page goes away with the release.
- The `errors.md` row `core.provider.not-available` shows an issue URL
template. The page is generated from the error catalogue, so changing it
means changing source.
- **Not counted.** `foundry-rs/foundry#17120` is a public upstream PR
that readers need. The tutorials' commit links (`7c4262e`, `0afbad3`,
`ab3ee2a`) show the exact code each run used, and they resolve once the
repository is public. `examples/` links stay because the examples are
written for users.

## Follow-up

The generated pages `docs/user/reference/api/README.md` and
`docs/user/reference/errors.md` keep a `Status:` line written for
maintainers ("Do not edit by hand; `pnpm run docs:check` fails…").
Changing them means changing `scripts/generate-api-docs.ts` and
`scripts/generate-errors-doc.ts`, so it is left for a separate change.

## How it was tested

- `pnpm run docs:check`, `pnpm run check`, `pnpm run pkg:check` and
`pnpm test` pass.
- `test/scripts/user-pages.test.ts` covers:
  - codes in prose, parentheses and link text, with line numbers;
- codes in inline code (including a double-backtick span), fences, a
four-backtick fence holding a three-backtick one, and indented code;
  - a list item's indented continuation, checked as prose;
- URLs and link targets, Apple chip names, and identifiers such as
`0xM5abc`;
- maintainer comments, comments in code, the skip marker, and the
pre-release note on its own page and on another page.
- lychee 0.24.2 offline over the CI file set reported 0 errors after the
first commit, and the new anchors resolve.

## AI use

Claude Code wrote the changes and this description. I reviewed every
edit against the reader notes for #75 (P1, P4, X10), the issue's
acceptance criteria and a separate QA pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
aelmanaa added a commit to aelmanaa/hardhat-kms that referenced this pull request Oct 4, 2026
…#269)

Closes #242

## What changes

- **Contributor architecture, "Cloud access and credentials"**
(`docs/contributor/architecture.md`). It has one flowchart each for AWS,
Google Cloud and Azure. Each shows the credential order, the config
fields that change it, and where the SDK clients are built, cached and
closed. A "Where each step is" list under each diagram cites `path:line`
for every arrow and order, in this repo or the SDK at the lockfile
versions.
- **User page, "How the plugin reaches your cloud"**
(`docs/user/explanation/cloud-access.md`). It has a precedence table per
provider, the three rules that decide which identity signs, and one
diagram each for a laptop, CI with OIDC, a server, VM or container, and
a long-lived secret in the environment. The three setup guides,
`how-it-works.md`, `docs/README.md` and `AGENTS.md` link it.
- **Foundry comparison**: a Credentials section with the AWS and Google
Cloud chains, the AWS profile difference, per-key `profile`, and the
Azure chain proposed in foundry-rs/foundry#17120.
`migrate-from-foundry.md` links it.
- **Audit fixes** in the existing diagrams and the text next to them
(table below).
- **Mermaid check**: `pnpm docs:check` parses every ` ```mermaid ` block
with `mermaid.parse()` (`scripts/mermaid-blocks.ts`, tests in
`test/scripts/mermaid-blocks.test.ts`, added to the root `test` script).
New dev dependencies: `mermaid` 12.1.0 and `dompurify` 3.4.16, both in
the catalog.

No code under `packages/` changes.

### Spike: `mermaid.parse()` in Node

It works, with one shim. In a scratch project, `mermaid` 12.1.0 imported
in Node 22 in about 0.6 s, and `parse()` accepted and refused sequence
diagrams and unknown diagram types correctly. Flowcharts and state
diagrams failed with `DOMPurify.addHook is not a function`. Without a
`window`, DOMPurify builds without its API; Mermaid sets up sanitising
hooks while it parses labels. Parsing needs only the syntax. So the
check gives Mermaid's copy of DOMPurify a no-op `addHook` and `sanitize`
before it imports Mermaid, and then flowcharts parse while broken ones
fail: an unclosed label, an edge without a target, or a node named
`call`. `dompurify` is a direct dev dependency at the version Mermaid
resolves, so the check and Mermaid share one copy; a second copy would
make the check fail loudly with that same `addHook` error, not pass
silently. No browser and no Puppeteer.

Cost: parsing the docs' Mermaid blocks adds under a second of CPU to
`docs:check` on a warm cache. The test file, which also loads Mermaid,
takes about 1.4 s of user CPU (`node --test
test/scripts/mermaid-blocks.test.ts`). `mermaid` also adds 117 packages
to the dev install. The check found a real error on its first run: the
Google Cloud diagram used `call` as a node id, which Mermaid reads as a
keyword. Mermaid's line number in its message does not match the block's
lines for flowcharts, so the check reports the block's opening fence and
Mermaid's text.

GitHub renders with its own Mermaid build, which can differ from 12.1.0.
Please open the three changed pages on this branch to confirm they
render: `docs/contributor/architecture.md`,
`docs/user/explanation/cloud-access.md`,
`docs/user/explanation/how-it-works.md`.

### Audit of the existing diagrams

| Diagram | Finding | Change |
| --- | --- | --- |
| `architecture.md` Module map | Azure missing from the provider
packages node; no `history/`; no history readers | Added Azure and
"history reader" to the packages node, and a `history/` node (`tasks`
imports `history`, which imports `registry`, `descriptors` and `signer`,
from the imports of `history/read.ts`, `history/mask.ts`,
`tasks/history.ts`). Removed the "blue = implemented" class: every
module is implemented, and it was the only brand colour. |
| `architecture.md` Packages table | One SDK per package | Lists
`@aws-sdk/client-cloudtrail` and `client-sts`; `google-gax` and
`google-auth-library`; `@azure/core-rest-pipeline` (from each
`package.json`). The SDK loading paragraph says the same. |
| `architecture.md` Sending a transaction | `nonce = max(pending,
high-water + 1)` | `max(pending, high-water + 1, highest reservation +
1)` (`send-guard.ts:579-592`). Also added the `eth_chainId` read before
the lock (`dispatcher.ts:628-636`) and the uncertain-transaction lookup
inside it (`dispatcher.ts:658-660`, `716`), with a cited paragraph
below. |
| `transactions.md` nonce paragraph (not a diagram) | Same stale rule |
Fixed, with a link to the reservations section. |
| `how-it-works.md` diagram | Current | No change. |
| `how-it-works.md` step 4 | Pre-#199 rule, and "the node's pending
count alone decides" on simulated networks, which reservations now
contradict | States the three numbers, and that only the high-water mark
is off on simulated networks. |
| `architecture.md` Signing a message, Library accounts | Checked
against `kms-signer.ts:197-237`, `viem/account.ts`, `viem/inputs.ts` |
No stale node. |
| #177 removed fields, #208 package names | `idempotencyKey`,
`approvalTimeoutMs`, `SignContext.chainId` and unscoped package names
appear in no diagram | No change. Directory paths keep
`packages/hardhat-kms-*`, which is correct. |
| Module layout blocks (text, not Mermaid) | Core tree missing
`history/` and the `history` task; AWS tree missing
`client-settings.ts`, `history.ts`, `history-api.ts` | Added. The code
map gains `client-settings` and a history readers row. |

### Facts that changed since #242 was filed

- Azure (#255): the plugin picks `ClientSecretCredential` or
`ClientCertificateCredential` itself, wraps it so any failure stops the
chain, and throws for username and password only when they would be
used. The diagram follows `credential.ts` at this base.
- AWS (#260): `profile` and `region` can be configuration variables,
read in `client-settings.ts` when the adapter is built; an empty value
is unset.
- Log Analytics host (#261): `api.loganalytics.azure.com`, with the
`api.loganalytics.io` token scope.
- #242 describes one Azure chain for the keys and the history reader. In
the code, `kms history` builds a new chain for each read
(`hook-handlers/kms.ts:77-85`); the keys share one per runtime. The docs
say so.

### Citation table

Repository paths are at 1a5b13d. SDK paths are inside each package in
`node_modules`, at the lockfile versions: `@aws-sdk/client-kms`
3.1143.0, `@aws-sdk/core` 3.978.1, `@aws-sdk/credential-provider-node`
3.972.84, `google-gax` 6.10.0, `google-auth-library` 11.1.0,
`@azure/identity` 4.13.3.

| Diagram step | Source |
| --- | --- |
| Signer cache keys signers by identity |
`packages/hardhat-kms/src/internal/signer/key-cache.ts:79-94` |
| Identity: AWS region, profile, endpoint; Google Cloud and Azure none |
`packages/hardhat-kms/src/internal/signer/signer-identity.ts:53-74` |
| A new signer runs the `createKeyAdapter` hook chain |
`key-cache.ts:149`,
`packages/hardhat-kms/src/internal/providers/create-adapter.ts:123-132`
|
| Idle close after 5 s closes the signers and adapters |
`key-cache.ts:13`, `key-cache.ts:104-145`,
`packages/hardhat-kms/src/internal/signer/kms-signer.ts:236-237` |
| `kms history` runs the `readSignHistory` hook chain |
`packages/hardhat-kms/src/internal/history/read.ts:111-127` |
| AWS handler loads adapter and SDK |
`packages/hardhat-kms-aws/src/internal/hook-handlers/kms.ts:48-63` |
| AWS region order |
`packages/hardhat-kms/src/internal/providers/aws/config.ts:98-107`,
`packages/hardhat-kms-aws/src/internal/client-settings.ts:11-14`,
`client-settings.ts:26-36` |
| `new KMSClient({ customUserAgent, region, profile, endpoint })` |
`packages/hardhat-kms-aws/src/internal/adapter.ts:175-184` |
| Client config reaches the default provider | `@aws-sdk/client-kms`
`dist-cjs/index.js:2175`; `@aws-sdk/core`
`dist-cjs/submodules/httpAuthSchemes/index.js:292-305` |
| AWS chain order; profile skips environment keys; SSO-in-code step
never applies | `@aws-sdk/credential-provider-node`
`dist-cjs/index.js:96-160`, `97-125`, `126-134` |
| Shared config, `credential_process`, web identity, remote |
`index.js:135-139`, `140-144`, `145-149`, `150-153` |
| Container credentials, else IMDS unless disabled | `index.js:5-19` |
| A non-"try next" error stops the chain; credentials memoised,
refreshed 5 min early | `index.js:78-93`, `index.js:21-77`,
`index.js:162` |
| AWS `destroy()` on idle close | `adapter.ts:148-149` |
| AWS history clients, destroyed after the read |
`packages/hardhat-kms-aws/src/internal/history-api.ts:94-97`, `119`,
`125-128`, `133-138`; `hook-handlers/kms.ts:78-92` |
| Google Cloud handler loads adapter, `@google-cloud/kms`, `google-gax`
| `packages/hardhat-kms-gcp/src/internal/hook-handlers/kms.ts:40-43`,
`94-105` |
| Adapter keeps a factory; client built on a call, `fallback: true` |
`packages/hardhat-kms-gcp/src/internal/adapter.ts:403-413`, `313-316`,
`366-369`, `409-411` |
| `google-gax` builds the `GoogleAuth` | `google-gax`
`build/src/fallback.js:141-153` |
| A failed `initialize()` closes and drops the client; error mapping |
`adapter.ts:370-381`, `adapter.ts:345-348`,
`packages/hardhat-kms-gcp/src/internal/wire.ts:128-143` |
| Google Cloud `close()` on idle close | `adapter.ts:256-258` |
| ADC order, no fallback after an unreadable file, quota project |
`google-auth-library` `build/src/auth/googleauth.js:245-286`, `257-267`,
`269-279`, `281-285`, `287-290`, `315-330`, `336-365` |
| Google Cloud history `GoogleAuth` per read |
`hook-handlers/kms.ts:52-58`, `88-92` |
| Azure factory and credential once per runtime; failed load retried |
`packages/hardhat-kms-azure/src/internal/hook-handlers/kms.ts:51-54`,
`57-67`, `123-141` |
| Chain built from the sources that apply; shared token per scope and
tenant |
`packages/hardhat-kms-azure/src/internal/credential.ts:443-484`,
`288-336` |
| Tenant id and username/password throws | `credential.ts:218-224`,
`277-280` |
| Source 1: secret, else certificate; options; failure stops the chain |
`credential.ts:185-188`, `248-282`, `158-178`, `238` |
| Source 2: workload identity only when its variables are set |
`credential.ts:454-458`; `@azure/identity`
`dist/commonjs/credentials/workloadIdentityCredential.js:50-70` |
| Source 3: `az`, then `azd` | `credential.ts:459` |
| Source 4: managed identity, client id, Cloud Shell and Service Fabric
exclusion, 10 s and 3 s | `credential.ts:460-472`, `386-398`, `16`,
`23`, `371-379`, `461-464`, `473-481`; `@azure/identity`
`dist/commonjs/credentials/managedIdentityCredential/index.js:106-137` |
| Chain goes on only on "unavailable"; error ids | `@azure/identity`
`dist/commonjs/credentials/chainedTokenCredential.js:79-98`;
`packages/hardhat-kms-azure/src/internal/adapter.ts:89-92`, `318-322` |
| Key Vault clients per signer; no adapter `close` | `adapter.ts:175`,
`212-217`, `389-392` |
| Azure history: new chain per read, Log Analytics host and scope |
`hook-handlers/kms.ts:77-85`,
`packages/hardhat-kms-azure/src/internal/log-analytics.ts:20-26`,
`56-60` |
| User page diagrams (laptop, CI, server, secret) | The same sources as
the rows above; the CI rows also rest on the READMEs of
`aws-actions/configure-aws-credentials`, `google-github-actions/auth`
and `azure/login`, not on a CI run |
| Foundry: AWS and Google Cloud construction |
foundry-rs/foundry-core@6228965 `crates/wallets/src/signer.rs:95-107`,
`116-130` (read through the GitHub API) |
| Foundry: AWS order, Google ADC order, proposed Azure chain |
`aws-config` 1.12.0 `src/default_provider/credentials.rs:183-193`,
`gcloud-sdk` 0.29.0 `src/token_source/mod.rs:60-77`, foundry#17120
`crates/wallets/src/azure.rs:31-82` at 30544e0, read on 2026-10-03 and
not read again for this PR. foundry#17120 and foundry-core#228 are still
open; alloy#4267 merged on 2026-09-30. |

The user page has no `path:line` citations: user pages stay in terms a
reader outside the project understands. It links to the contributor
section, marked optional, which carries them.

## How it was tested

- `pnpm docs:check` passes. It failed first on the `call` node id, which
shows the new check catches a broken block.
- `node --test test/scripts/mermaid-blocks.test.ts`: 9 cases pass: no
blocks; a valid flowchart; a valid sequence diagram; an unclosed label;
an edge without a target; an unknown diagram type; a keyword node id; a
broken sequence message; and a mix of valid and broken blocks, including
a pie chart.
- `pnpm check` passes; `knip` reports nothing.
- `prose-slop-gate.py --diff origin/main`: 0 findings.
- Only the Mermaid tests ran locally: no package test suite and no cloud
calls. CI runs the rest, and lychee in the CI Docs job checks the links
and anchors; `docs:check` does not.

## Docs

- New: `docs/user/explanation/cloud-access.md`, linked from `AGENTS.md`
and `docs/README.md`.
- Changed: `docs/contributor/architecture.md`,
`docs/contributor/transactions.md`, `docs/contributor/documentation.md`
(the seventh check), `docs/user/explanation/how-it-works.md`,
`docs/user/explanation/foundry-comparison.md`, the three setup guides,
`docs/user/guides/migrate-from-foundry.md`, `AGENTS.md` and
`CONTRIBUTING.md` (the `docs:check` comment).

## AI use

Claude Code (Claude Opus 5.5) wrote this change: the spike, the check,
the diagrams and the text. It read the source and the installed SDK
files cited above. The maintainer reviews before merge.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

This pull request is stale because it has been open for 14 days with no activity. It will be closed in 7 days if there is no further activity.

@github-actions github-actions Bot added the stale label Oct 14, 2026
@mablr mablr added stop-stale and removed stale labels Oct 14, 2026
Wire the `azure-key-vault` wallet feature from foundry-wallets through
cast and forge, and enable it in release builds. `--azure` signs with an
Azure Key Vault or Managed HSM `P-256K` key selected by
`AZURE_KEY_VAULT_KEY_ID`, and is also accepted by `cast wallet list`,
Tempo sessions, and `--tempo.sponsor-signer azure://`.

foundry-core and alloy are patched from git until both are released.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@aelmanaa
aelmanaa force-pushed the feat/azure-key-vault-signer branch from e7a2b06 to 994f338 Compare October 18, 2026 22:59

This branch has not been deployed

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

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

feat: support Azure Key Vault signer (--azure)

2 participants