diff --git a/.github/workflows/sdk_build.yml b/.github/workflows/sdk_build.yml
index 48ee20782..5751be8d1 100644
--- a/.github/workflows/sdk_build.yml
+++ b/.github/workflows/sdk_build.yml
@@ -356,7 +356,8 @@ jobs:
files=$(ls packages/*.nupkg | jq -R -s -c '
split("\n")[:-1]
| map(select(
- (test("/Dapr\\.Workflow\\.Versioning\\.(Abstractions|Generators|Runtime)\\.")) | not
+ ((test("/Dapr\\.Workflow\\.Versioning\\.(Abstractions|Generators|Runtime)\\.")) | not) and
+ ((test("/Dapr\\.SecretsManagement\\.(Abstractions|Generators|Runtime)\\.")) | not)
))
')
echo "matrix=$files" >> $GITHUB_OUTPUT
diff --git a/all.sln b/all.sln
index 47e798512..91cfaef72 100644
--- a/all.sln
+++ b/all.sln
@@ -260,6 +260,19 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.IntegrationTest.Actors
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowRetryPolicy", "examples\Workflow\WorkflowRetryPolicy\WorkflowRetryPolicy.csproj", "{6C77A2C4-0A96-4B90-AFEA-16E86A906259}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.SecretsManagement.Abstractions", "src\Dapr.SecretsManagement.Abstractions\Dapr.SecretsManagement.Abstractions.csproj", "{366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.SecretsManagement.Runtime", "src\Dapr.SecretsManagement.Runtime\Dapr.SecretsManagement.Runtime.csproj", "{F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.SecretsManagement.Generators", "src\Dapr.SecretsManagement.Generators\Dapr.SecretsManagement.Generators.csproj", "{B42DD6AA-255C-4606-8A1B-263B26650DED}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.SecretsManagement", "src\Dapr.SecretsManagement\Dapr.SecretsManagement.csproj", "{1BECAC48-1C83-43D2-A91F-9AFA634A68C7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.SecretsManagement.Runtime.Test", "test\Dapr.SecretsManagement.Runtime.Test\Dapr.SecretsManagement.Runtime.Test.csproj", "{87842296-C78B-42B9-8E96-5054E79CD700}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SecretManagement", "SecretManagement", "{929A8AD2-DB45-B92A-7930-EBDD2DBAF802}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SecretManagementSample", "examples\SecretManagement\SecretManagementSample\SecretManagementSample.csproj", "{ED74B33F-3CE7-42EB-BAA1-623F43900B15}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.AI.Microsoft.Extensions.Test", "test\Dapr.AI.Microsoft.Extensions.Test\Dapr.AI.Microsoft.Extensions.Test.csproj", "{86CBB08F-601A-4B0B-87BF-383D391A961C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.DistributedLock.Test", "test\Dapr.DistributedLock.Test\Dapr.DistributedLock.Test.csproj", "{2B8E9CAD-F9A2-43B9-BB1C-619CF64476A0}"
@@ -1498,6 +1511,78 @@ Global
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Debug|x64.Build.0 = Debug|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Debug|x86.Build.0 = Debug|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Release|x64.ActiveCfg = Release|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Release|x64.Build.0 = Release|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Release|x86.ActiveCfg = Release|Any CPU
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E}.Release|x86.Build.0 = Release|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Debug|x64.Build.0 = Debug|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Debug|x86.Build.0 = Debug|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Release|x64.ActiveCfg = Release|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Release|x64.Build.0 = Release|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Release|x86.ActiveCfg = Release|Any CPU
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2}.Release|x86.Build.0 = Release|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Debug|x64.Build.0 = Debug|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Debug|x86.Build.0 = Debug|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Release|x64.ActiveCfg = Release|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Release|x64.Build.0 = Release|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Release|x86.ActiveCfg = Release|Any CPU
+ {B42DD6AA-255C-4606-8A1B-263B26650DED}.Release|x86.Build.0 = Release|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Debug|x64.Build.0 = Debug|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Debug|x86.Build.0 = Debug|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Release|x64.ActiveCfg = Release|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Release|x64.Build.0 = Release|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Release|x86.ActiveCfg = Release|Any CPU
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7}.Release|x86.Build.0 = Release|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Debug|x64.Build.0 = Debug|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Debug|x86.Build.0 = Debug|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Release|Any CPU.Build.0 = Release|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Release|x64.ActiveCfg = Release|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Release|x64.Build.0 = Release|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Release|x86.ActiveCfg = Release|Any CPU
+ {87842296-C78B-42B9-8E96-5054E79CD700}.Release|x86.Build.0 = Release|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Debug|x64.Build.0 = Debug|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Debug|x86.Build.0 = Debug|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Release|x64.ActiveCfg = Release|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Release|x64.Build.0 = Release|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Release|x86.ActiveCfg = Release|Any CPU
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15}.Release|x86.Build.0 = Release|Any CPU
{8777EAD2-419B-4683-826A-82B7C1F4F69F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8777EAD2-419B-4683-826A-82B7C1F4F69F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8777EAD2-419B-4683-826A-82B7C1F4F69F}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -1681,6 +1766,13 @@ Global
{01A20A89-53A1-4D5B-B563-89E157718474} = {8462B106-175A-423A-BA94-BE0D39D0BD8E}
{7B14879F-156B-417E-ACA3-0B5A69CC2F39} = {8462B106-175A-423A-BA94-BE0D39D0BD8E}
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {8462B106-175A-423A-BA94-BE0D39D0BD8E}
+ {366FA402-B13D-4EE2-9BA4-A0C3134C6C6E} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {F99CE5A1-FDA1-415C-B1E6-C8787734ACD2} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {B42DD6AA-255C-4606-8A1B-263B26650DED} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {1BECAC48-1C83-43D2-A91F-9AFA634A68C7} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {87842296-C78B-42B9-8E96-5054E79CD700} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {929A8AD2-DB45-B92A-7930-EBDD2DBAF802} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
+ {ED74B33F-3CE7-42EB-BAA1-623F43900B15} = {929A8AD2-DB45-B92A-7930-EBDD2DBAF802}
{6C77A2C4-0A96-4B90-AFEA-16E86A906259} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}
{86CBB08F-601A-4B0B-87BF-383D391A961C} = {0AF0FE8D-C234-4F04-8514-32206ACE01BD}
{2B8E9CAD-F9A2-43B9-BB1C-619CF64476A0} = {0AF0FE8D-C234-4F04-8514-32206ACE01BD}
diff --git a/examples/SecretManagement/README.md b/examples/SecretManagement/README.md
new file mode 100644
index 000000000..9e523c8ba
--- /dev/null
+++ b/examples/SecretManagement/README.md
@@ -0,0 +1,37 @@
+# Dapr Secrets Management Sample
+
+This sample demonstrates how to use the Dapr Secrets Management SDK to retrieve secrets from Dapr secret store components.
+
+## Features Demonstrated
+
+1. **Direct secret retrieval** — Using `DaprSecretsManagementClient` to fetch individual or bulk secrets via gRPC.
+2. **Typed secret stores** — Using the `[SecretStore]` and `[Secret]` attributes with the source generator to create strongly-typed secret accessors.
+3. **Dependency injection** — Registering the secrets client and typed stores via `IServiceCollection` extensions.
+
+## Prerequisites
+
+- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
+- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
+- A configured Dapr secret store component (e.g., local file, Kubernetes secrets, Azure Key Vault)
+
+## Running the Sample
+
+```bash
+dapr run --app-id secret-sample --app-port 6543 -- dotnet run
+```
+
+## Endpoints
+
+| Method | Path | Description |
+|--------|------|-------------|
+| GET | `/secrets/{storeName}/{key}` | Retrieve a single secret by key |
+| GET | `/secrets/{storeName}` | Retrieve all secrets from a store |
+| GET | `/typed-secrets` | Retrieve secrets using the source-generated typed store |
+
+## NuGet Package Note
+
+When consuming from NuGet, install the single **`Dapr.SecretsManagement`** package. The sub-projects (`Abstractions`, `Runtime`, `Generators`) are bundled into this one package and are not published individually.
+
+```xml
+
+```
diff --git a/examples/SecretManagement/SecretManagementSample/IMyVaultSecrets.cs b/examples/SecretManagement/SecretManagementSample/IMyVaultSecrets.cs
new file mode 100644
index 000000000..18eca9b68
--- /dev/null
+++ b/examples/SecretManagement/SecretManagementSample/IMyVaultSecrets.cs
@@ -0,0 +1,40 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.SecretsManagement.Abstractions;
+
+namespace SecretManagementSample;
+
+///
+/// Example of a typed secret store interface. Apply the to an interface
+/// and the Dapr Secrets Management source generator will produce:
+/// 1. A concrete implementation that caches secrets loaded at startup.
+/// 2. A DI registration extension method (e.g., AddMyVaultSecrets()).
+///
+/// Properties without use the property name as the secret key.
+/// Properties with use the specified secret name.
+///
+[SecretStore("my-vault")]
+public partial interface IMyVaultSecrets
+{
+ ///
+ /// The database connection string, retrieved from the "db-connection-string" secret key.
+ ///
+ [Secret("db-connection-string")]
+ string DatabaseConnection { get; }
+
+ ///
+ /// The API key. Uses the property name "ApiKey" as the secret key.
+ ///
+ string ApiKey { get; }
+}
diff --git a/examples/SecretManagement/SecretManagementSample/Program.cs b/examples/SecretManagement/SecretManagementSample/Program.cs
new file mode 100644
index 000000000..963a5be7b
--- /dev/null
+++ b/examples/SecretManagement/SecretManagementSample/Program.cs
@@ -0,0 +1,58 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.SecretsManagement;
+using Dapr.SecretsManagement.Extensions;
+using SecretManagementSample;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Register the Dapr Secrets Management client and the source-generated typed secret store.
+// AddMyVaultSecrets() is a generated extension method — see IMyVaultSecrets.cs.
+builder.Services.AddDaprSecretsManagementClient()
+ .AddMyVaultSecrets();
+
+var app = builder.Build();
+
+// --- Example 1: Direct secret retrieval ---
+app.MapGet("/secrets/{storeName}/{key}", async (
+ string storeName,
+ string key,
+ DaprSecretsManagementClient secretsClient,
+ CancellationToken cancellationToken) =>
+{
+ var secret = await secretsClient.GetSecretAsync(storeName, key, cancellationToken: cancellationToken);
+ return Results.Ok(secret);
+});
+
+// --- Example 2: Bulk secret retrieval ---
+app.MapGet("/secrets/{storeName}", async (
+ string storeName,
+ DaprSecretsManagementClient secretsClient,
+ CancellationToken cancellationToken) =>
+{
+ var secrets = await secretsClient.GetBulkSecretAsync(storeName, cancellationToken: cancellationToken);
+ return Results.Ok(secrets);
+});
+
+// --- Example 3: Using the source-generated typed secret store ---
+app.MapGet("/typed-secrets", (SecretManagementSample.IMyVaultSecrets secrets) =>
+{
+ return Results.Ok(new
+ {
+ DatabaseConnection = secrets.DatabaseConnection,
+ ApiKey = secrets.ApiKey
+ });
+});
+
+app.Run();
diff --git a/examples/SecretManagement/SecretManagementSample/Properties/launchSettings.json b/examples/SecretManagement/SecretManagementSample/Properties/launchSettings.json
new file mode 100644
index 000000000..33cfac2df
--- /dev/null
+++ b/examples/SecretManagement/SecretManagementSample/Properties/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "profiles": {
+ "SecretManagementSample": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:6543",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/examples/SecretManagement/SecretManagementSample/SecretManagementSample.csproj b/examples/SecretManagement/SecretManagementSample/SecretManagementSample.csproj
new file mode 100644
index 000000000..50dc200bd
--- /dev/null
+++ b/examples/SecretManagement/SecretManagementSample/SecretManagementSample.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+ true
+ $(BaseIntermediateOutputPath)Generated
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dapr.Common/AssemblyInfo.cs b/src/Dapr.Common/AssemblyInfo.cs
index 5875274af..612d2da6a 100644
--- a/src/Dapr.Common/AssemblyInfo.cs
+++ b/src/Dapr.Common/AssemblyInfo.cs
@@ -26,6 +26,7 @@
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Workflow, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Workflow.Grpc, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
+[assembly: InternalsVisibleTo("Dapr.SecretsManagement.Runtime, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Actors.AspNetCore.IntegrationTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Actors.AspNetCore.IntegrationTest.App, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
@@ -49,3 +50,4 @@
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Jobs.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Messaging.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
+[assembly: InternalsVisibleTo("Dapr.SecretsManagement.Runtime.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
diff --git a/src/Dapr.SecretsManagement.Abstractions/Dapr.SecretsManagement.Abstractions.csproj b/src/Dapr.SecretsManagement.Abstractions/Dapr.SecretsManagement.Abstractions.csproj
new file mode 100644
index 000000000..abba95de4
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Abstractions/Dapr.SecretsManagement.Abstractions.csproj
@@ -0,0 +1,8 @@
+
+
+
+ enable
+ enable
+
+
+
diff --git a/src/Dapr.SecretsManagement.Abstractions/SecretAttribute.cs b/src/Dapr.SecretsManagement.Abstractions/SecretAttribute.cs
new file mode 100644
index 000000000..211d9256a
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Abstractions/SecretAttribute.cs
@@ -0,0 +1,56 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.SecretsManagement.Abstractions;
+
+///
+/// Specifies the secret key name used when retrieving the value of the annotated property from a Dapr
+/// secret store. When this attribute is omitted, the property name is used as the secret key.
+///
+///
+/// This attribute should be applied to properties on an interface that is also annotated with
+/// . The source generator uses this metadata to map each property
+/// to the correct secret key during bulk secret retrieval.
+///
+///
+///
+/// [SecretStore("my-vault")]
+/// public partial interface IMySecrets
+/// {
+/// [Secret("database-connection-string")]
+/// string DbConnection { get; }
+/// }
+///
+///
+[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
+public sealed class SecretAttribute : Attribute
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The name of the secret key in the Dapr secret store. This value is used when calling the
+ /// Dapr Secrets API to retrieve the secret value.
+ ///
+ /// Thrown when is .
+ public SecretAttribute(string secretName)
+ {
+ ArgumentNullException.ThrowIfNull(secretName);
+ SecretName = secretName;
+ }
+
+ ///
+ /// Gets the name of the secret key in the Dapr secret store.
+ ///
+ public string SecretName { get; }
+}
diff --git a/src/Dapr.SecretsManagement.Abstractions/SecretStoreAttribute.cs b/src/Dapr.SecretsManagement.Abstractions/SecretStoreAttribute.cs
new file mode 100644
index 000000000..7e8ab73cb
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Abstractions/SecretStoreAttribute.cs
@@ -0,0 +1,68 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.SecretsManagement.Abstractions;
+
+///
+/// Marks an interface as a typed accessor for a Dapr secret store. When applied to a partial interface,
+/// the Dapr Secrets Management source generator will produce a concrete implementation that retrieves secrets
+/// from the specified Dapr secret store component and registers it in the dependency injection container.
+///
+///
+///
+/// The interface should declare -typed read-only properties. Each property maps to a
+/// single secret key in the store. The key name defaults to the property name but can be overridden with
+/// .
+///
+///
+/// Generated implementations load all mapped secrets in bulk at startup via an IHostedService and
+/// expose them as synchronous properties. This makes secrets available immediately after host startup without
+/// requiring callers to manage async flows.
+///
+///
+///
+/// [SecretStore("my-vault")]
+/// public partial interface IMySecrets
+/// {
+/// /// <summary>The database connection string.</summary>
+/// [Secret("db-connection-string")]
+/// string DatabaseConnection { get; }
+///
+/// /// <summary>The API key (uses property name as secret key).</summary>
+/// string ApiKey { get; }
+/// }
+///
+///
+///
+[AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
+public sealed class SecretStoreAttribute : Attribute
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The name of the Dapr secret store component to retrieve secrets from. This must match the
+ /// metadata.name of a configured Dapr secret store component.
+ ///
+ /// Thrown when is .
+ public SecretStoreAttribute(string storeName)
+ {
+ ArgumentNullException.ThrowIfNull(storeName);
+ StoreName = storeName;
+ }
+
+ ///
+ /// Gets the name of the Dapr secret store component that secrets will be retrieved from.
+ ///
+ public string StoreName { get; }
+}
diff --git a/src/Dapr.SecretsManagement.Abstractions/WellKnownSecrets.cs b/src/Dapr.SecretsManagement.Abstractions/WellKnownSecrets.cs
new file mode 100644
index 000000000..909f50303
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Abstractions/WellKnownSecrets.cs
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+namespace Dapr.SecretsManagement.Abstractions;
+
+///
+/// Well-known names and constants related to Dapr Secrets Management.
+///
+internal static class WellKnownSecrets
+{
+ ///
+ /// The fully qualified name of .
+ ///
+ public const string SecretStoreAttributeFullName = "Dapr.SecretsManagement.Abstractions.SecretStoreAttribute";
+
+ ///
+ /// The fully qualified name of .
+ ///
+ public const string SecretAttributeFullName = "Dapr.SecretsManagement.Abstractions.SecretAttribute";
+}
diff --git a/src/Dapr.SecretsManagement.Generators/Dapr.SecretsManagement.Generators.csproj b/src/Dapr.SecretsManagement.Generators/Dapr.SecretsManagement.Generators.csproj
new file mode 100644
index 000000000..063de25a9
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Generators/Dapr.SecretsManagement.Generators.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+ netstandard2.0
+ false
+ enable
+
+ true
+ true
+
+ Dapr.SecretsManagement
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
diff --git a/src/Dapr.SecretsManagement.Generators/KnownSymbols.cs b/src/Dapr.SecretsManagement.Generators/KnownSymbols.cs
new file mode 100644
index 000000000..b869974d9
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Generators/KnownSymbols.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Microsoft.CodeAnalysis;
+
+namespace Dapr.SecretsManagement;
+
+///
+/// Caches well-known Roslyn symbols used during source generation.
+///
+internal sealed record KnownSymbols(
+ INamedTypeSymbol? SecretStoreAttribute,
+ INamedTypeSymbol? SecretAttribute);
diff --git a/src/Dapr.SecretsManagement.Generators/SecretStoreSourceGenerator.cs b/src/Dapr.SecretsManagement.Generators/SecretStoreSourceGenerator.cs
new file mode 100644
index 000000000..89d032f02
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Generators/SecretStoreSourceGenerator.cs
@@ -0,0 +1,308 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Dapr.SecretsManagement;
+
+///
+/// Incremental source generator that discovers interfaces annotated with [SecretStore],
+/// reads their property mappings (including optional [Secret] attributes), and emits:
+///
+/// - A concrete implementation class that stores secret values retrieved from the Dapr secret store.
+/// - A DI registration extension method on IDaprSecretsManagementBuilder.
+///
+///
+[Generator(LanguageNames.CSharp)]
+public sealed class SecretStoreSourceGenerator : IIncrementalGenerator
+{
+ private const string SecretStoreAttributeFullName = "Dapr.SecretsManagement.Abstractions.SecretStoreAttribute";
+ private const string SecretAttributeFullName = "Dapr.SecretsManagement.Abstractions.SecretAttribute";
+
+ ///
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Cache the attribute symbols.
+ var known = context.CompilationProvider.Select((c, _) =>
+ new KnownSymbols(
+ SecretStoreAttribute: c.GetTypeByMetadataName(SecretStoreAttributeFullName),
+ SecretAttribute: c.GetTypeByMetadataName(SecretAttributeFullName)));
+
+ // Report diagnostic if the attribute types are not found.
+ context.RegisterSourceOutput(known, (spc, ks) =>
+ {
+ if (ks.SecretStoreAttribute is null)
+ {
+ spc.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(
+ "DAPR1601",
+ "SecretStore attribute not found",
+ "The source generator could not find the SecretStoreAttribute type. " +
+ "Ensure that the Dapr.SecretsManagement package is properly referenced.",
+ "Dapr.SecretsManagement",
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true),
+ Location.None));
+ }
+ });
+
+ // Discover candidate interface declarations with at least one attribute.
+ var candidates = context.SyntaxProvider.CreateSyntaxProvider(
+ predicate: static (node, _) => node is InterfaceDeclarationSyntax ids && ids.AttributeLists.Count > 0,
+ transform: static (ctx, ct) =>
+ {
+ var interfaceSyntax = (InterfaceDeclarationSyntax)ctx.Node;
+ var symbol = ctx.SemanticModel.GetDeclaredSymbol(interfaceSyntax, ct);
+ return symbol as INamedTypeSymbol;
+ })
+ .Where(static s => s is not null)!;
+
+ // Combine candidates with known symbols and generate.
+ var combined = candidates.Combine(known);
+
+ context.RegisterSourceOutput(combined, (spc, pair) =>
+ {
+ var (interfaceSymbol, ks) = pair;
+ if (ks.SecretStoreAttribute is null || interfaceSymbol is null)
+ return;
+
+ // Check if the interface has [SecretStore] attribute.
+ var storeAttrData = interfaceSymbol.GetAttributes().FirstOrDefault(a =>
+ SymbolEqualityComparer.Default.Equals(a.AttributeClass, ks.SecretStoreAttribute));
+
+ if (storeAttrData is null)
+ return;
+
+ // Extract the store name from the constructor argument.
+ if (storeAttrData.ConstructorArguments.Length < 1 ||
+ storeAttrData.ConstructorArguments[0].Value is not string storeName)
+ return;
+
+ // Collect properties and their secret key mappings.
+ var properties = new List<(string PropertyName, string SecretKey, string PropertyType)>();
+
+ foreach (var member in interfaceSymbol.GetMembers())
+ {
+ if (member is not IPropertySymbol prop)
+ continue;
+
+ if (prop.GetMethod is null)
+ continue;
+
+ var secretKey = prop.Name;
+
+ // Check for [Secret("key")] override.
+ if (ks.SecretAttribute is not null)
+ {
+ var secretAttrData = prop.GetAttributes().FirstOrDefault(a =>
+ SymbolEqualityComparer.Default.Equals(a.AttributeClass, ks.SecretAttribute));
+
+ if (secretAttrData is not null &&
+ secretAttrData.ConstructorArguments.Length > 0 &&
+ secretAttrData.ConstructorArguments[0].Value is string overrideName)
+ {
+ secretKey = overrideName;
+ }
+ }
+
+ properties.Add((prop.Name, secretKey, prop.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)));
+ }
+
+ if (properties.Count == 0)
+ return;
+
+ // Resolve naming.
+ var interfaceName = interfaceSymbol.Name;
+ var namespaceName = interfaceSymbol.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : interfaceSymbol.ContainingNamespace.ToDisplayString();
+
+ // Implementation class name: strip leading 'I' from interface name, append "SecretStoreClient".
+ var implName = interfaceName.StartsWith("I", StringComparison.Ordinal) && interfaceName.Length > 1 && char.IsUpper(interfaceName[1])
+ ? interfaceName.Substring(1) + "SecretStoreClient"
+ : interfaceName + "SecretStoreClient";
+
+ var source = GenerateSource(
+ namespaceName, interfaceName, implName, storeName,
+ interfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
+ properties);
+
+ var fqn = interfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ var hintName = implName + "_" + GetStableHash(fqn).ToString("X8") + ".g.cs";
+ spc.AddSource(hintName, source);
+ });
+ }
+
+ private static string GenerateSource(
+ string? namespaceName,
+ string interfaceName,
+ string implName,
+ string storeName,
+ string fullyQualifiedInterfaceName,
+ List<(string PropertyName, string SecretKey, string PropertyType)> properties)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("// ");
+ sb.AppendLine("#nullable enable");
+ sb.AppendLine();
+ sb.AppendLine("using System;");
+ sb.AppendLine("using System.CodeDom.Compiler;");
+ sb.AppendLine("using System.Collections.Generic;");
+ sb.AppendLine("using System.Threading;");
+ sb.AppendLine("using System.Threading.Tasks;");
+ sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
+ sb.AppendLine("using Microsoft.Extensions.Hosting;");
+ sb.AppendLine();
+
+ if (namespaceName is not null)
+ {
+ sb.AppendLine($"namespace {namespaceName}");
+ sb.AppendLine("{");
+ }
+
+ // 1. Implementation class
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// Auto-generated implementation of that retrieves secrets from the");
+ sb.AppendLine($" /// Dapr secret store component named {EscapeXml(storeName)}.");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" [GeneratedCode(\"Dapr.SecretsManagement.Generators\", \"1.0.0\")]");
+ sb.AppendLine($" internal sealed class {implName} : {fullyQualifiedInterfaceName}");
+ sb.AppendLine(" {");
+ sb.AppendLine(" private readonly Dictionary _secrets;");
+ sb.AppendLine();
+ sb.AppendLine($" internal {implName}(Dictionary secrets)");
+ sb.AppendLine(" {");
+ sb.AppendLine(" _secrets = secrets ?? throw new ArgumentNullException(nameof(secrets));");
+ sb.AppendLine(" }");
+ sb.AppendLine();
+
+ foreach (var (propName, secretKey, propType) in properties)
+ {
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" public {propType} {propName} =>");
+ sb.AppendLine($" _secrets.TryGetValue(\"{EscapeCSharpString(secretKey)}\", out var __{propName}Value)");
+ sb.AppendLine($" ? __{propName}Value");
+ sb.AppendLine($" : throw new KeyNotFoundException(\"Secret '{EscapeCSharpString(secretKey)}' was not found in store '{EscapeCSharpString(storeName)}'.\");");
+ sb.AppendLine();
+ }
+
+ sb.AppendLine(" }");
+ sb.AppendLine();
+
+ // 2. Hosted service that loads secrets at startup
+ var loaderName = implName.Replace("SecretStoreClient", "SecretStoreLoader");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// Hosted service that pre-loads secrets from the {EscapeXml(storeName)} Dapr secret store");
+ sb.AppendLine($" /// at application startup and registers the generated in the container.");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" [GeneratedCode(\"Dapr.SecretsManagement.Generators\", \"1.0.0\")]");
+ sb.AppendLine($" internal sealed class {loaderName} : IHostedService");
+ sb.AppendLine(" {");
+ sb.AppendLine($" private readonly Dapr.SecretsManagement.DaprSecretsManagementClient _client;");
+ sb.AppendLine($" private Dictionary? _secrets;");
+ sb.AppendLine();
+ sb.AppendLine($" public {loaderName}(Dapr.SecretsManagement.DaprSecretsManagementClient client)");
+ sb.AppendLine(" {");
+ sb.AppendLine(" _client = client ?? throw new ArgumentNullException(nameof(client));");
+ sb.AppendLine(" }");
+ sb.AppendLine();
+ sb.AppendLine(" internal Dictionary Secrets =>");
+ sb.AppendLine(" _secrets ?? throw new InvalidOperationException(");
+ sb.AppendLine($" \"Secrets from store '{EscapeCSharpString(storeName)}' have not been loaded yet. Ensure the host has started.\");");
+ sb.AppendLine();
+ sb.AppendLine(" public async Task StartAsync(CancellationToken cancellationToken)");
+ sb.AppendLine(" {");
+ sb.AppendLine($" var bulk = await _client.GetBulkSecretAsync(\"{EscapeCSharpString(storeName)}\", cancellationToken: cancellationToken).ConfigureAwait(false);");
+ sb.AppendLine(" var flat = new Dictionary();");
+ sb.AppendLine(" foreach (var entry in bulk)");
+ sb.AppendLine(" {");
+ sb.AppendLine(" foreach (var kvp in entry.Value)");
+ sb.AppendLine(" {");
+ sb.AppendLine(" flat[kvp.Key] = kvp.Value;");
+ sb.AppendLine(" }");
+ sb.AppendLine(" }");
+ sb.AppendLine(" _secrets = flat;");
+ sb.AppendLine(" }");
+ sb.AppendLine();
+ sb.AppendLine(" public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;");
+ sb.AppendLine(" }");
+ sb.AppendLine();
+
+ // 3. DI registration extension
+ var methodName = "Add" + (interfaceName.StartsWith("I", StringComparison.Ordinal) && interfaceName.Length > 1 && char.IsUpper(interfaceName[1])
+ ? interfaceName.Substring(1)
+ : interfaceName);
+
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// Extension methods for registering the generated implementation.");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" [GeneratedCode(\"Dapr.SecretsManagement.Generators\", \"1.0.0\")]");
+ sb.AppendLine($" public static class {implName}Extensions");
+ sb.AppendLine(" {");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// Registers the generated typed secret store implementation for ");
+ sb.AppendLine($" /// with the dependency injection container. Secrets are loaded from the {EscapeXml(storeName)}");
+ sb.AppendLine($" /// Dapr secret store at application startup via an .");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// The Dapr Secrets Management builder.");
+ sb.AppendLine($" /// The builder instance for chaining.");
+ sb.AppendLine($" public static Dapr.SecretsManagement.IDaprSecretsManagementBuilder {methodName}(");
+ sb.AppendLine($" this Dapr.SecretsManagement.IDaprSecretsManagementBuilder builder)");
+ sb.AppendLine(" {");
+ sb.AppendLine($" builder.Services.AddSingleton<{loaderName}>();");
+ sb.AppendLine($" builder.Services.AddSingleton(sp => sp.GetRequiredService<{loaderName}>());");
+ sb.AppendLine($" builder.Services.AddSingleton<{fullyQualifiedInterfaceName}>(sp =>");
+ sb.AppendLine($" new {implName}(sp.GetRequiredService<{loaderName}>().Secrets));");
+ sb.AppendLine(" return builder;");
+ sb.AppendLine(" }");
+ sb.AppendLine(" }");
+
+ if (namespaceName is not null)
+ {
+ sb.AppendLine("}");
+ }
+
+ return sb.ToString();
+ }
+
+ private static string EscapeCSharpString(string value) =>
+ value.Replace("\\", "\\\\").Replace("\"", "\\\"");
+
+ private static string EscapeXml(string value) =>
+ value.Replace("&", "&").Replace("<", "<").Replace(">", ">");
+
+ ///
+ /// Produces a stable, deterministic 32-bit hash from the given string.
+ /// Used to generate unique hint names when multiple types share the same simple name
+ /// across different namespaces.
+ ///
+ private static uint GetStableHash(string text)
+ {
+ unchecked
+ {
+ uint hash = 2166136261;
+ foreach (var ch in text)
+ {
+ hash = (hash ^ ch) * 16777619;
+ }
+ return hash;
+ }
+ }
+}
diff --git a/src/Dapr.SecretsManagement.Runtime/AssemblyInfo.cs b/src/Dapr.SecretsManagement.Runtime/AssemblyInfo.cs
new file mode 100644
index 000000000..b6f1304b3
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/AssemblyInfo.cs
@@ -0,0 +1,16 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Dapr.SecretsManagement.Runtime.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2" )]
diff --git a/src/Dapr.SecretsManagement.Runtime/Dapr.SecretsManagement.Runtime.csproj b/src/Dapr.SecretsManagement.Runtime/Dapr.SecretsManagement.Runtime.csproj
new file mode 100644
index 000000000..a9648a8e2
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/Dapr.SecretsManagement.Runtime.csproj
@@ -0,0 +1,21 @@
+
+
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementClient.cs b/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementClient.cs
new file mode 100644
index 000000000..335d7a8e5
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementClient.cs
@@ -0,0 +1,120 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.Common;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;
+
+namespace Dapr.SecretsManagement;
+
+///
+///
+/// Defines client operations for managing Dapr secrets. Use
+/// to create a or register for use with dependency injection via
+/// .
+///
+///
+/// Implementations of implement because the
+/// client accesses network resources. For best performance, create a single long-lived client instance and share
+/// it for the lifetime of the application. This is done for you if created via the DI extensions. Avoid creating
+/// and disposing a client instance for each operation that the application performs — this can lead to socket
+/// exhaustion and other problems.
+///
+///
+public abstract class DaprSecretsManagementClient(
+ Autogenerated.DaprClient client,
+ HttpClient httpClient,
+ string? daprApiToken = null) : IDaprClient
+{
+ private bool disposed;
+
+ ///
+ /// The HTTP client used by the client for calling the Dapr runtime.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly HttpClient HttpClient = httpClient;
+
+ ///
+ /// The Dapr API token value.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly string? DaprApiToken = daprApiToken;
+
+ ///
+ /// The autogenerated Dapr gRPC client.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal Autogenerated.DaprClient Client { get; } = client;
+
+ ///
+ /// Gets the secret value for a specific key from the specified secret store.
+ ///
+ /// The name of the Dapr secret store component.
+ /// The secret key to retrieve.
+ ///
+ /// An optional collection of metadata key-value pairs that will be provided to the secret store component.
+ /// The valid metadata keys and values are determined by the type of secret store used.
+ ///
+ /// A that can be used to cancel the operation.
+ ///
+ /// A that resolves to a containing the secret
+ /// data. Some secret stores (such as Kubernetes) can store multiple values per key — each entry in the
+ /// dictionary represents one such value.
+ ///
+ public abstract Task> GetSecretAsync(
+ string storeName,
+ string key,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets all secret values that the application is allowed to access from the specified secret store.
+ ///
+ /// The name of the Dapr secret store component.
+ ///
+ /// An optional collection of metadata key-value pairs that will be provided to the secret store component.
+ /// The valid metadata keys and values are determined by the type of secret store used.
+ ///
+ /// A that can be used to cancel the operation.
+ ///
+ /// A that resolves to a nested dictionary. The outer key is the secret name; the inner
+ /// dictionary contains one or more key-value pairs representing the secret data.
+ ///
+ public abstract Task>> GetBulkSecretAsync(
+ string storeName,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ public void Dispose()
+ {
+ if (!this.disposed)
+ {
+ Dispose(disposing: true);
+ this.disposed = true;
+ }
+ }
+
+ ///
+ /// Disposes the resources associated with the object.
+ ///
+ /// if called by a call to the method; otherwise .
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+}
diff --git a/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementClientBuilder.cs b/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementClientBuilder.cs
new file mode 100644
index 000000000..99c8320e1
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementClientBuilder.cs
@@ -0,0 +1,37 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.Common;
+using Microsoft.Extensions.Configuration;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.SecretsManagement;
+
+///
+/// Builds a instance with the specified configuration.
+///
+/// An optional instance used to resolve default settings.
+public sealed class DaprSecretsManagementClientBuilder(IConfiguration? configuration = null)
+ : DaprGenericClientBuilder(configuration)
+{
+ ///
+ /// Builds the instance from the properties configured on this builder.
+ ///
+ /// A new instance.
+ public override DaprSecretsManagementClient Build()
+ {
+ var daprClientDependencies = this.BuildDaprClientDependencies(typeof(DaprSecretsManagementClient).Assembly);
+ var client = new Autogenerated.Dapr.DaprClient(daprClientDependencies.channel);
+ return new DaprSecretsManagementGrpcClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken);
+ }
+}
diff --git a/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementGrpcClient.cs b/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementGrpcClient.cs
new file mode 100644
index 000000000..ad918418f
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/DaprSecretsManagementGrpcClient.cs
@@ -0,0 +1,137 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.Common;
+using Grpc.Core;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.SecretsManagement;
+
+///
+/// A gRPC-backed implementation of that communicates with the
+/// Dapr sidecar to retrieve secrets from configured secret store components.
+///
+internal sealed class DaprSecretsManagementGrpcClient(
+ Autogenerated.Dapr.DaprClient client,
+ HttpClient httpClient,
+ string? daprApiToken = null)
+ : DaprSecretsManagementClient(client, httpClient, daprApiToken: daprApiToken)
+{
+ ///
+ public override async Task> GetSecretAsync(
+ string storeName,
+ string key,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(storeName);
+ ArgumentException.ThrowIfNullOrEmpty(key);
+
+ var envelope = new Autogenerated.GetSecretRequest
+ {
+ StoreName = storeName,
+ Key = key
+ };
+
+ if (metadata is not null)
+ {
+ foreach (var kvp in metadata)
+ {
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
+ }
+ }
+
+ var callOptions = DaprClientUtilities.ConfigureGrpcCallOptions(
+ typeof(DaprSecretsManagementClient).Assembly,
+ this.DaprApiToken,
+ cancellationToken);
+
+ try
+ {
+ var response = await Client.GetSecretAsync(envelope, callOptions).ConfigureAwait(false);
+ return response.Data.ToDictionary(kv => kv.Key, kv => kv.Value);
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ throw;
+ }
+ catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled && cancellationToken.IsCancellationRequested)
+ {
+ throw new OperationCanceledException(cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ throw new DaprException(
+ "Get secret operation failed: the Dapr endpoint indicated a failure. See InnerException for details.",
+ ex);
+ }
+ }
+
+ ///
+ public override async Task>> GetBulkSecretAsync(
+ string storeName,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(storeName);
+
+ var envelope = new Autogenerated.GetBulkSecretRequest
+ {
+ StoreName = storeName
+ };
+
+ if (metadata is not null)
+ {
+ foreach (var kvp in metadata)
+ {
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
+ }
+ }
+
+ var callOptions = DaprClientUtilities.ConfigureGrpcCallOptions(
+ typeof(DaprSecretsManagementClient).Assembly,
+ this.DaprApiToken,
+ cancellationToken);
+
+ try
+ {
+ var response = await Client.GetBulkSecretAsync(envelope, callOptions).ConfigureAwait(false);
+ return response.Data.ToDictionary(
+ r => r.Key,
+ r => (IReadOnlyDictionary)r.Value.Secrets.ToDictionary(s => s.Key, s => s.Value));
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ throw;
+ }
+ catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled && cancellationToken.IsCancellationRequested)
+ {
+ throw new OperationCanceledException(cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ throw new DaprException(
+ "Bulk secret operation failed: the Dapr endpoint indicated a failure. See InnerException for details.",
+ ex);
+ }
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ this.HttpClient.Dispose();
+ }
+ }
+}
diff --git a/src/Dapr.SecretsManagement.Runtime/Extensions/DaprSecretsManagementBuilder.cs b/src/Dapr.SecretsManagement.Runtime/Extensions/DaprSecretsManagementBuilder.cs
new file mode 100644
index 000000000..8719856ea
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/Extensions/DaprSecretsManagementBuilder.cs
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.SecretsManagement.Extensions;
+
+///
+/// Used by the fluent registration builder to configure a Dapr Secrets Management client.
+///
+/// The service collection to register services with.
+public sealed class DaprSecretsManagementBuilder(IServiceCollection services) : IDaprSecretsManagementBuilder
+{
+ ///
+ /// Gets the registered services on the builder.
+ ///
+ public IServiceCollection Services { get; } = services;
+}
diff --git a/src/Dapr.SecretsManagement.Runtime/Extensions/DaprSecretsManagementServiceCollectionExtensions.cs b/src/Dapr.SecretsManagement.Runtime/Extensions/DaprSecretsManagementServiceCollectionExtensions.cs
new file mode 100644
index 000000000..bc5b50258
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/Extensions/DaprSecretsManagementServiceCollectionExtensions.cs
@@ -0,0 +1,39 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.Common.Extensions;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.SecretsManagement.Extensions;
+
+///
+/// Contains extension methods for using Dapr Secrets Management with dependency injection.
+///
+public static class DaprSecretsManagementServiceCollectionExtensions
+{
+ ///
+ /// Adds Dapr Secrets Management client support to the service collection.
+ ///
+ /// The to add services to.
+ ///
+ /// An optional callback used to configure the with injected services.
+ ///
+ /// The lifetime of the registered services. Defaults to .
+ /// An that can be used for further configuration.
+ public static IDaprSecretsManagementBuilder AddDaprSecretsManagementClient(
+ this IServiceCollection services,
+ Action? configure = null,
+ ServiceLifetime lifetime = ServiceLifetime.Singleton) =>
+ services.AddDaprClient(
+ configure, lifetime);
+}
diff --git a/src/Dapr.SecretsManagement.Runtime/IDaprSecretsManagementBuilder.cs b/src/Dapr.SecretsManagement.Runtime/IDaprSecretsManagementBuilder.cs
new file mode 100644
index 000000000..e05c81eea
--- /dev/null
+++ b/src/Dapr.SecretsManagement.Runtime/IDaprSecretsManagementBuilder.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.Common;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.SecretsManagement;
+
+///
+/// Responsible for registering Dapr Secrets Management service functionality.
+///
+public interface IDaprSecretsManagementBuilder : IDaprServiceBuilder;
diff --git a/src/Dapr.SecretsManagement/Dapr.SecretsManagement.csproj b/src/Dapr.SecretsManagement/Dapr.SecretsManagement.csproj
new file mode 100644
index 000000000..6bfabacb1
--- /dev/null
+++ b/src/Dapr.SecretsManagement/Dapr.SecretsManagement.csproj
@@ -0,0 +1,162 @@
+
+
+
+ true
+ Dapr.SecretsManagement
+ Dapr Secrets Management SDK for .NET
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+ $(NoWarn);NU5128
+
+
+ false
+
+
+
+ $(TargetsForTfmSpecificContentInPackage);
+ _BuildChildrenForCurrentTFM;
+ _CollectChildOutputsForCurrentTFM;
+ _AddAnalyzerOnce
+
+
+
+ $(TargetsForPack);_BuildGeneratorOnce;_AddAnalyzer
+
+
+
+
+
+ <_ExpectedDirectoryBuildProps>$(MSBuildProjectDirectory)\..\Directory.Build.props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_EffectiveTfmFolder>$(NuGetTargetFrameworkFolderName)
+ <_EffectiveTfmFolder Condition="'$(_EffectiveTfmFolder)'==''">$(TargetFramework)
+
+
+
+
+
+
+
+ <_ChildDll Include="@(_BuiltChildOutputs)" Condition="'%(_BuiltChildOutputs.Extension)'=='.dll'" />
+
+
+ <_ChildXml Include="%(_ChildDll.RootDir)%(_ChildDll.Directory)%(_ChildDll.Filename).xml"
+ Condition="Exists('%(_ChildDll.RootDir)%(_ChildDll.Directory)%(_ChildDll.Filename).xml')" />
+ <_ChildPdb Include="%(_ChildDll.RootDir)%(_ChildDll.Directory)%(_ChildDll.Filename).pdb"
+ Condition="Exists('%(_ChildDll.RootDir)%(_ChildDll.Directory)%(_ChildDll.Filename).pdb')" />
+
+
+
+ $(TargetFramework)
+ lib/$(_EffectiveTfmFolder)/%(_ChildDll.Filename)%(_ChildDll.Extension)
+
+
+
+ $(TargetFramework)
+ lib/$(_EffectiveTfmFolder)/%(_ChildXml.Filename).xml
+
+
+
+ $(TargetFramework)
+ lib/$(_EffectiveTfmFolder)/%(_ChildPdb.Filename).pdb
+
+
+
+
+
+
+
+
+ <_IsFirstTfm Condition="'$(TargetFramework)' == 'net8.0'">true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_AddAnalyzer Condition="'$(TargetFramework)' == 'net8.0'">true
+
+
+
+
+ $(TargetFramework)
+ analyzers/dotnet/cs/%(_AnalyzerOut.Filename)%(_AnalyzerOut.Extension)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Dapr.SecretsManagement.Runtime.Test/Dapr.SecretsManagement.Runtime.Test.csproj b/test/Dapr.SecretsManagement.Runtime.Test/Dapr.SecretsManagement.Runtime.Test.csproj
new file mode 100644
index 000000000..4561e68b0
--- /dev/null
+++ b/test/Dapr.SecretsManagement.Runtime.Test/Dapr.SecretsManagement.Runtime.Test.csproj
@@ -0,0 +1,29 @@
+
+
+
+ enable
+ enable
+ false
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementClientBuilderTests.cs b/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementClientBuilderTests.cs
new file mode 100644
index 000000000..b32e1e998
--- /dev/null
+++ b/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementClientBuilderTests.cs
@@ -0,0 +1,101 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Text.Json;
+using Grpc.Net.Client;
+
+namespace Dapr.SecretsManagement.Test;
+
+public sealed class DaprSecretsManagementClientBuilderTests
+{
+ [Fact]
+ public void Builder_UsesPropertyNameCaseInsensitiveByDefault()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ Assert.True(builder.JsonSerializerOptions.PropertyNameCaseInsensitive);
+ }
+
+ [Fact]
+ public void Builder_UsesPropertyNameCaseHandlingAsSpecified()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ builder.UseJsonSerializationOptions(new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = false
+ });
+ Assert.False(builder.JsonSerializerOptions.PropertyNameCaseInsensitive);
+ }
+
+ [Fact]
+ public void Builder_UsesThrowOperationCanceledOnCancellation_ByDefault()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ _ = builder.Build();
+ Assert.True(builder.GrpcChannelOptions.ThrowOperationCanceledOnCancellation);
+ }
+
+ [Fact]
+ public void Builder_DoesNotOverrideUserGrpcChannelOptions()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ _ = builder.UseGrpcChannelOptions(new GrpcChannelOptions()).Build();
+ Assert.False(builder.GrpcChannelOptions.ThrowOperationCanceledOnCancellation);
+ }
+
+ [Fact]
+ public void Builder_ValidatesGrpcEndpointScheme()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ builder.UseGrpcEndpoint("ftp://example.com");
+
+ var ex = Assert.Throws(() => builder.Build());
+ Assert.Equal("The gRPC endpoint must use http or https.", ex.Message);
+ }
+
+ [Fact]
+ public void Builder_ValidatesHttpEndpointScheme()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ builder.UseHttpEndpoint("ftp://example.com");
+
+ var ex = Assert.Throws(() => builder.Build());
+ Assert.Equal("The HTTP endpoint must use http or https.", ex.Message);
+ }
+
+ [Fact]
+ public void Builder_SetsApiToken()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ builder.UseDaprApiToken("test_token");
+ _ = builder.Build();
+ Assert.Equal("test_token", builder.DaprApiToken);
+ }
+
+ [Fact]
+ public void Builder_SetsNullApiToken()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ builder.UseDaprApiToken(null!);
+ _ = builder.Build();
+ Assert.Null(builder.DaprApiToken);
+ }
+
+ [Fact]
+ public void Builder_SetsTimeout()
+ {
+ var builder = new DaprSecretsManagementClientBuilder();
+ builder.UseTimeout(TimeSpan.FromSeconds(2));
+ _ = builder.Build();
+ Assert.Equal(2, builder.Timeout.Seconds);
+ }
+}
diff --git a/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementGrpcClientTests.cs b/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementGrpcClientTests.cs
new file mode 100644
index 000000000..1856c27fc
--- /dev/null
+++ b/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementGrpcClientTests.cs
@@ -0,0 +1,100 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Moq;
+
+namespace Dapr.SecretsManagement.Test;
+
+public sealed class DaprSecretsManagementGrpcClientTests
+{
+ [Fact]
+ public async Task GetSecretAsync_ThrowsOnNullStoreName()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprSecretsManagementGrpcClient(mockClient, httpClient, null);
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ await client.GetSecretAsync(null!, "my-key", cancellationToken: TestContext.Current.CancellationToken);
+ });
+ }
+
+ [Fact]
+ public async Task GetSecretAsync_ThrowsOnNullKey()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprSecretsManagementGrpcClient(mockClient, httpClient, null);
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ await client.GetSecretAsync("my-store", null!, cancellationToken: TestContext.Current.CancellationToken);
+ });
+ }
+
+ [Fact]
+ public async Task GetSecretAsync_ThrowsOnEmptyKey()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprSecretsManagementGrpcClient(mockClient, httpClient, null);
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ await client.GetSecretAsync("my-store", "", cancellationToken: TestContext.Current.CancellationToken);
+ });
+ }
+
+ [Fact]
+ public async Task GetBulkSecretAsync_ThrowsOnNullStoreName()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = Mock.Of();
+
+ var client = new DaprSecretsManagementGrpcClient(mockClient, httpClient, null);
+
+ await Assert.ThrowsAsync(async () =>
+ {
+ await client.GetBulkSecretAsync(null!, cancellationToken: TestContext.Current.CancellationToken);
+ });
+ }
+
+ [Fact]
+ public void Dispose_CanBeCalledWithoutException()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = new HttpClient();
+
+ var client = new DaprSecretsManagementGrpcClient(mockClient, httpClient, null);
+
+ // Should not throw.
+ client.Dispose();
+ }
+
+ [Fact]
+ public void Dispose_IsIdempotent()
+ {
+ var mockClient = Mock.Of();
+ var httpClient = new HttpClient();
+
+ var client = new DaprSecretsManagementGrpcClient(mockClient, httpClient, null);
+
+ // Calling Dispose multiple times should not throw.
+ client.Dispose();
+ client.Dispose();
+ }
+}
diff --git a/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementServiceCollectionExtensionsTests.cs b/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementServiceCollectionExtensionsTests.cs
new file mode 100644
index 000000000..1aff866d3
--- /dev/null
+++ b/test/Dapr.SecretsManagement.Runtime.Test/DaprSecretsManagementServiceCollectionExtensionsTests.cs
@@ -0,0 +1,54 @@
+// ------------------------------------------------------------------------
+// Copyright 2026 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using Dapr.SecretsManagement.Extensions;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.SecretsManagement.Test;
+
+public sealed class DaprSecretsManagementServiceCollectionExtensionsTests
+{
+ [Fact]
+ public void AddDaprSecretsManagementClient_RegistersClientInServiceCollection()
+ {
+ var services = new ServiceCollection();
+ var builder = services.AddDaprSecretsManagementClient();
+
+ Assert.NotNull(builder);
+ Assert.IsAssignableFrom(builder);
+
+ var descriptor = services.FirstOrDefault(sd => sd.ServiceType == typeof(DaprSecretsManagementClient));
+ Assert.NotNull(descriptor);
+ Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
+ }
+
+ [Fact]
+ public void AddDaprSecretsManagementClient_RespectsCustomLifetime()
+ {
+ var services = new ServiceCollection();
+ services.AddDaprSecretsManagementClient(lifetime: ServiceLifetime.Transient);
+
+ var descriptor = services.FirstOrDefault(sd => sd.ServiceType == typeof(DaprSecretsManagementClient));
+ Assert.NotNull(descriptor);
+ Assert.Equal(ServiceLifetime.Transient, descriptor.Lifetime);
+ }
+
+ [Fact]
+ public void AddDaprSecretsManagementClient_BuilderExposesSameServices()
+ {
+ var services = new ServiceCollection();
+ var builder = services.AddDaprSecretsManagementClient();
+
+ Assert.Same(services, builder.Services);
+ }
+}