---
title: Secrets Plugin
description: Resolve allowlisted logical secret references without putting secret values in the App Plan.
---

`lenso.secrets@1` exposes one `resolve` request. Consumers ask for a logical
reference such as `database/url`; App Composition binds that requirement to one
provider. Secret values never belong in Plan configuration, diagnostics,
errors, or `Debug` output.

## Configure the Env provider

The linked Rust Env provider is intended for local development and controlled
host deployments:

```json title="Plugin configuration"
{
  "references": {
    "database/url": "APP_DATABASE_URL",
    "auth/signing-key": "APP_AUTH_SIGNING_KEY"
  }
}
```

Register its factory in the native App and bind the consumer's exactly-one
`lenso.secrets@1` requirement to that Instance:

```rust
use lenso_native_adapter::NativePluginRegistry;
use lenso_secrets_env_module::EnvSecretsFactory;

let native = NativePluginRegistry::new()
    .with_factory(EnvSecretsFactory::new());
```

The allowlist must be non-empty. Logical references are canonical paths up to
256 bytes: no leading or trailing slash, empty segment, `.` segment, or `..`
segment. Duplicate references and non-portable environment variable names fail
Plan admission.

## Resolve from a Plugin

```rust
use lenso_capability_secrets::{
    ResolveRequest, SecretsClient, SecretsInvocationError,
};
use lenso_kernel::{PluginDependencies, RuntimeFailure};

fn client(dependencies: &PluginDependencies) -> Result<SecretsClient, RuntimeFailure> {
    SecretsClient::from_dependencies(dependencies)
}

async fn database_url(secrets: &SecretsClient) -> Result<String, SecretsInvocationError> {
    let response = secrets.resolve(ResolveRequest {
        reference: "database/url".into(),
    }).await?;
    Ok(response.value)
}
```

`invalid_reference` means the request is malformed;
`unknown_reference` means it is valid but absent from this Instance's
allowlist. An unavailable configured source is a Runtime Failure, not a Domain
Error and not a signal to try another provider.

During `prepare`, the Env provider verifies every configured source without
printing the environment variable name or value. It reads the current process
environment again for every `resolve`, so a host-side rotation is observed
without changing the Plan. If the source later disappears, resolution fails
truthfully.

## Implement another provider

A production cloud provider belongs in its own Plugin. It implements the
generated `SecretsProvider`, returns only the requested value, and owns its
authentication, rotation, availability, and audit policy:

```rust
use futures::future::LocalBoxFuture;
use lenso_capability_secrets::{
    ResolveRequest, ResolveResponse, SecretsInvocationError, SecretsProvider,
};
use lenso_kernel::InvocationContext;

#[derive(Debug)]
struct CloudSecrets;

impl SecretsProvider for CloudSecrets {
    fn resolve(
        &self,
        context: InvocationContext,
        request: ResolveRequest,
    ) -> LocalBoxFuture<'static, Result<ResolveResponse, SecretsInvocationError>> {
        Box::pin(async move {
            // Authenticate to the selected backend, authorize `request.reference`,
            // fetch the value, and map expected failures explicitly.
            todo!()
        })
    }
}
```

Do not add implicit provider fallback or put a cloud SDK in the Kernel.

:::warning
The current Secrets Descriptor is native-only (`portable: false`) and does not
allow cross-lane transfer. The maintained Env provider is a linked Rust Plugin.
A Bun Plugin cannot consume it across a process lane merely because TypeScript
types can be generated; it needs an adapter-local provider or a future contract
whose portability and threat model are explicit.
:::

The owner repository currently marks registry publication as parked. Check the
package registry before relying on `cargo add`; source implementation and
registry availability are separate facts.
