Skip to content

feat(daemon): namespaced memory + keychain secret stores - #113

Merged
cidrblock merged 19 commits into
redhat-developer:mainfrom
cidrblock:feat/in-memory-secret-store
Aug 13, 2026
Merged

feat(daemon): namespaced memory + keychain secret stores#113
cidrblock merged 19 commits into
redhat-developer:mainfrom
cidrblock:feat/in-memory-secret-store

Conversation

@cidrblock

@cidrblock cidrblock commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Wire discrete secret backends via SecretStoreRegistry (MemorySecretStore + KeychainSecretStore); values addressed by (secret_store, secret_name) — overlapping names allowed across backends (no overlay)
  • Prefer secret_name + secret_store (memory | keychain | env); keep api_key_keychain_name / api_key_env_var_name as YAML legacy aliases
  • Provider resolve uses only the configured backend (NamespacedSecretStore.getFrom); omitted store defaults to keychain
  • SetSecret / HTTP secrets write memory|keychain only (env rejected — env is a credential source, not a registered store); Get/Delete take optional store (default keychain); List emits one row per backend that holds a key
  • ConfigureProvider / HTTP configure pick an existing named secret in a specific store (N providers → 1 key); raw api_key alone keeps the legacy invent-name shortcut
  • Provider remove only deletes owned store secrets (${PROVIDER}_API_KEY / abbenay.<id>); skips secret_store: env and shared picks
  • Document as DR-047

Out of scope (follow-up)

Commits

  • feat(daemon): add process-lifetime in-memory secret store
  • feat(daemon): let ConfigureProvider pick a named secret
  • refactor(daemon): prefer secret_name and secret_store vocabulary
  • feat(daemon): allow secret_store=env for provider references
  • fix(daemon): narrow secret_store writes to memory|keychain for tsc
  • refactor(daemon): namespace secrets by (store, name) via SecretStoreRegistry
  • fix(daemon): align secret remove/status with namespaced stores
  • fix(daemon): normalize keytar missing values to null
  • test(daemon): cover namespaced secret store resolve and remove paths

Test plan

  • Focused vitest (registry + grpc secrets + configure + consumer-auth)
  • daemon vitest + focused integration coverage
  • Verify SetSecret MEMORY + GetSecret store=MEMORY; keychain Get does not see it
  • Verify same name can exist in memory and keychain independently
  • Verify ConfigureProvider with secret_name + secret_store=memory resolves that backend only
  • Verify secret_name + secret_store=env configures without writing the secret store
  • Verify SetSecret / HTTP with secret_store=env returns INVALID_ARGUMENT / 400
  • Verify RemoveProvider does not delete env-backed or shared secret names
  • Unit tests for providerCredentialSource / isProviderOwnedSecretName
  • resolveApiKey reads only the configured namespaced backend
  • HTTP DELETE secret_store=memory leaves keychain intact; key-status reports backends independently
  • gRPC ListSecrets emits one row per backend holding an engine key

Wire DualSecretStore so clients can SetSecret with store=MEMORY without
persisting to the OS keychain. Enforces move-on-write exclusivity and
fails closed if the other backend cannot be cleared (DR-047).
Copilot AI lite review requested due to automatic review settings August 12, 2026 15:51
@github-actions github-actions Bot added the feat label Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cidrblock, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: edb459bf-dca3-4cac-b4c1-b0103b50b10a

📥 Commits

Reviewing files that changed from the base of the PR and between 7abfae0 and 7edbbe3.

📒 Files selected for processing (34)
  • docs/ARCHITECTURE.md
  • docs/CONFIGURATION.md
  • docs/SECURITY.md
  • docs/decisions.md
  • packages/daemon/src/core/config-schema.ts
  • packages/daemon/src/core/config.test.ts
  • packages/daemon/src/core/config.ts
  • packages/daemon/src/core/index.ts
  • packages/daemon/src/core/secrets.ts
  • packages/daemon/src/core/state.test.ts
  • packages/daemon/src/core/state.ts
  • packages/daemon/src/daemon/secrets/keychain.test.ts
  • packages/daemon/src/daemon/secrets/keychain.ts
  • packages/daemon/src/daemon/secrets/registry.test.ts
  • packages/daemon/src/daemon/secrets/registry.ts
  • packages/daemon/src/daemon/server/abbenay-service.test.ts
  • packages/daemon/src/daemon/server/abbenay-service.ts
  • packages/daemon/src/daemon/server/consumer-auth.test.ts
  • packages/daemon/src/daemon/state.ts
  • packages/daemon/src/daemon/web/api-schemas.test.ts
  • packages/daemon/src/daemon/web/api-schemas.ts
  • packages/daemon/src/daemon/web/server.ts
  • packages/daemon/tests/helpers/with-env.test.ts
  • packages/daemon/tests/helpers/with-env.ts
  • packages/daemon/tests/integration/consumer-auth.test.ts
  • packages/daemon/tests/integration/grpc-real-service.test.ts
  • packages/daemon/tests/integration/web-sse.test.ts
  • packages/proto-ts/src/abbenay/v1/service.ts
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.pyi
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2_grpc.py
  • packages/vscode/src/proto/abbenay/v1/service.ts
  • proto/abbenay/v1/service.proto
  • sonar-project.properties
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added optional memory-backed secret storage through HTTP and gRPC APIs.
    • Secret listings now show whether values are stored in memory or the OS keychain.
    • Secrets can be moved between supported backends; memory-stored values are cleared when the daemon restarts.
    • Environment-backed secrets remain read-only and cannot be written through secret APIs.
  • Documentation

    • Documented storage choices, security considerations, defaults, and backend behavior.
  • Tests

    • Added coverage for backend selection, switching, deletion, validation, and API integration.

Walkthrough

The daemon now uses a composite memory and keychain secret store. gRPC and HTTP APIs support backend selection, report secret locations, reject environment-backed writes, and include integration and unit test coverage.

Changes

Dual secret storage

Layer / File(s) Summary
Dual store behavior
packages/daemon/src/daemon/secrets/dual.ts, packages/daemon/src/daemon/secrets/dual.test.ts
Adds memory-first reads, keychain-default writes, move-on-write exclusivity, deletion, backend detection, input parsing, and protocol conversion with unit tests.
Daemon and gRPC integration
packages/daemon/src/daemon/state.ts, packages/daemon/src/daemon/server/abbenay-service.ts, packages/daemon/tests/integration/grpc-real-service.test.ts
Initializes DaemonState with both backends. gRPC secret operations accept store selections, report locations, audit memory writes, and reject environment-backed writes.
HTTP API integration
packages/daemon/src/daemon/web/api-schemas.ts, packages/daemon/src/daemon/web/server.ts, packages/daemon/tests/integration/web-sse.test.ts
HTTP schemas and endpoints support memory, keychain, and env selections. Responses report the selected backend, and environment-backed writes return errors.
Architecture and security documentation
docs/ARCHITECTURE.md, docs/CONFIGURATION.md, docs/SECURITY.md, docs/decisions.md, packages/daemon/src/core/secrets.ts
Documents the dual store, memory-secret lifecycle, backend selection, audit sources, and security considerations.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SecretAPI
  participant DualSecretStore
  participant MemorySecretStore
  participant KeychainSecretStore
  Client->>SecretAPI: Submit secret and store selection
  SecretAPI->>DualSecretStore: Validate and write secret
  DualSecretStore->>MemorySecretStore: Update memory backend
  DualSecretStore->>KeychainSecretStore: Update keychain backend
  SecretAPI-->>Client: Return secret backend metadata
Loading

Suggested reviewers: sudhirverma

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the daemon change to use namespaced memory and keychain secret stores.
Description check ✅ Passed The description directly explains the secret-store design, API behavior, tests, documentation, and follow-up scope.
Linked Issues check ✅ Passed The description identifies relevant follow-up issues for the out-of-scope UI and pluggable-backend work.
Out of Scope Changes check ✅ Passed The changes match the stated objective of adding memory and keychain secret-store support and documenting DR-047.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.98305% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.58%. Comparing base (98cd5b4) to head (7edbbe3).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #113      +/-   ##
==========================================
+ Coverage   78.02%   78.58%   +0.55%     
==========================================
  Files          38       38              
  Lines        4979     5276     +297     
  Branches     1569     1709     +140     
==========================================
+ Hits         3885     4146     +261     
- Misses        542      557      +15     
- Partials      552      573      +21     
Flag Coverage Δ
daemon 78.58% <88.98%> (+0.55%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a process-lifetime in-memory secret backend alongside the existing OS keychain backend, allowing clients to choose where secrets are stored (with move-on-write mutual exclusivity) and rejecting writable ENV secrets.

Changes:

  • Introduces DualSecretStore (memory + keychain) with mutual exclusivity and “fail closed” semantics when clearing the other backend fails.
  • Extends HTTP and gRPC secret-setting APIs to accept a store selector (memory/keychain) and reject env writes.
  • Updates integration/unit tests and security/configuration/architecture docs to describe DR-047 and the new behavior.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/daemon/tests/integration/web-sse.test.ts Adds HTTP integration coverage for store: "memory" and rejecting store: "env".
packages/daemon/tests/integration/grpc-real-service.test.ts Adds gRPC integration coverage for memory-only storage, move-on-write behavior, and rejecting ENV writes.
packages/daemon/src/daemon/web/server.ts Adds store selection for HTTP secret writes and reports backend in GET /api/secrets.
packages/daemon/src/daemon/web/api-schemas.ts Extends request schemas to accept optional store field.
packages/daemon/src/daemon/web/api-schemas.test.ts Adds schema tests for accepted store values.
packages/daemon/src/daemon/state.ts Switches daemon default secret store to DualSecretStore(memory, keychain).
packages/daemon/src/daemon/server/abbenay-service.ts Adds gRPC store parsing/dispatch and reports backend in ListSecrets.
packages/daemon/src/daemon/secrets/dual.ts Implements DualSecretStore, store parsing, and proto mapping helpers.
packages/daemon/src/daemon/secrets/dual.test.ts Adds unit tests for dual-store semantics and store parsing/mapping.
packages/daemon/src/core/secrets.ts Updates documentation/comments to reflect dual-store usage and audit sources.
docs/SECURITY.md Documents in-memory secret implications and threat model considerations.
docs/decisions.md Adds DR-047 decision record for process-lifetime secrets.
docs/CONFIGURATION.md Documents configuration semantics and how to use the in-memory backend via secrets APIs.
docs/ARCHITECTURE.md Updates architecture docs to reference DualSecretStore and its role.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/daemon/src/daemon/web/server.ts Outdated
Comment thread packages/daemon/src/daemon/server/abbenay-service.ts Outdated
Comment thread packages/daemon/src/daemon/secrets/dual.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/decisions.md`:
- Around line 837-847: Update DualSecretStore.setIn to prevent or explicitly
handle dual residency when rollback fails, including defined failure and
recovery behavior. Revise docs/decisions.md lines 837-847 and
docs/CONFIGURATION.md lines 407-408 so both guarantees accurately describe the
implementation and rollback failure outcome.

In `@packages/daemon/src/daemon/secrets/dual.ts`:
- Around line 78-87: Update the verification flow around other.has(key) to catch
rejected verification calls, delete the newly written primary secret as
rollback, ignore rollback deletion errors, and throw the same clear-failure
error used when the other backend still contains the key. Preserve the existing
successful verification and fail-closed behavior.
- Around line 56-88: Serialize all per-key mutations in the dual secret backend:
update setIn and the corresponding delete operation to use the same keyed mutex
or promise queue, holding it across every await until the operation completes or
rolls back. Preserve existing rollback and error behavior, and add a parallel
move test demonstrating concurrent operations cannot leave the key absent or
resident in both backends.

In `@packages/daemon/src/daemon/state.ts`:
- Around line 87-89: Update the HTTP and gRPC GetKeyStatus handlers to preserve
keychain-only semantics: when secretStore is the dual store, determine presence
with await dual.locate(name) === 'keychain' instead of has(name); retain
has(name) for non-dual stores. Use the DualSecretStore instance configured in
the daemon state initialization.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 42712a66-4ecf-4d2b-9242-0c805e62117e

📥 Commits

Reviewing files that changed from the base of the PR and between b265a2f and 7abfae0.

📒 Files selected for processing (14)
  • docs/ARCHITECTURE.md
  • docs/CONFIGURATION.md
  • docs/SECURITY.md
  • docs/decisions.md
  • packages/daemon/src/core/secrets.ts
  • packages/daemon/src/daemon/secrets/dual.test.ts
  • packages/daemon/src/daemon/secrets/dual.ts
  • packages/daemon/src/daemon/server/abbenay-service.ts
  • packages/daemon/src/daemon/state.ts
  • packages/daemon/src/daemon/web/api-schemas.test.ts
  • packages/daemon/src/daemon/web/api-schemas.ts
  • packages/daemon/src/daemon/web/server.ts
  • packages/daemon/tests/integration/grpc-real-service.test.ts
  • packages/daemon/tests/integration/web-sse.test.ts

Comment thread docs/decisions.md Outdated
Comment thread packages/daemon/src/daemon/secrets/dual.ts Outdated
Comment thread packages/daemon/src/daemon/secrets/dual.ts Outdated
Comment thread packages/daemon/src/daemon/state.ts
Add api_key_keychain_name so providers reference an existing SetSecret
key (N:1). Raw api_key alone keeps the legacy invent-name shortcut.
Copilot AI review requested due to automatic review settings August 12, 2026 15:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (2)

packages/daemon/src/daemon/secrets/dual.ts:144

  • The ENV-write rejection message is gRPC-specific (mentions SetSecret and api_key_env_var_name). This same string is returned by the HTTP secrets endpoints too, so HTTP callers will get a confusing error that references the wrong API shape.
  if (store === 'env' || store === 'SECRET_STORE_ENV' || store === 2 || store === '2') {
    return {
      ok: false,
      error:
        'SECRET_STORE_ENV is not writable via SetSecret; use api_key_env_var_name in provider config',
    };

packages/daemon/tests/integration/consumer-auth.test.ts:295

  • This test title says it rejects a missing api_key_keychain_name, but the request includes api_key_keychain_name and the actual failure case is that the referenced secret does not exist. Renaming the test would make the intent clearer and easier to diagnose when it fails.
  it('ConfigureProvider rejects missing api_key_keychain_name', async () => {

Rename the logical secret lookup to secret_name (YAML still accepts
api_key_keychain_name). ConfigureProvider/HTTP use secret_name plus
optional secret_store when writing a raw api_key.
Copilot AI review requested due to automatic review settings August 12, 2026 16:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (3)

packages/daemon/src/daemon/secrets/dual.ts:143

  • parseSecretStoreChoice() is used from multiple entry points (gRPC SetSecret, HTTP secrets endpoints, and provider configuration). The ENV error message currently mentions “via SetSecret”, which can be confusing when returned from non-SetSecret code paths. Consider making the message backend-agnostic (e.g., “not writable via secrets APIs”).
    return {
      ok: false,
      error:
        'SECRET_STORE_ENV is not writable via SetSecret; use api_key_env_var_name in provider config',
    };

packages/daemon/src/daemon/secrets/dual.ts:87

  • DualSecretStore.setIn doesn’t check the boolean return from other.delete(). For backends that signal failure via a false return (e.g., keytar deletePassword failures are caught and return false), the current code may treat the key as cleared as long as other.has() returns false, which can violate the “fails closed / mutual exclusivity” guarantee. Capture the delete result and fail/rollback when the other backend previously had the key but deletion reports false.
    try {
      await other.delete(key);
    } catch (error: unknown) {
      // Roll back primary so we do not leave a dual-resident key.
      try {

packages/daemon/src/core/secrets.ts:36

  • auditSecretChange() source documentation is missing the newly introduced "*-configure-memory" sources used by the HTTP/gRPC configure paths (e.g., "http-configure-memory" and "grpc-configure-memory"), which makes the comment misleading for operators grepping logs.
  op: 'set' | 'delete';
  /** http-secrets | http-secrets-memory | grpc-secrets | grpc-secrets-memory | http-configure | grpc-configure | core-add */
  source: string;

secret_name + secret_store=env points at a process env var (resolved at
request time). SetSecret still rejects ENV writes; legacy env_var_name maps
to the same shape.
Copilot AI review requested due to automatic review settings August 12, 2026 16:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (4)

packages/daemon/src/daemon/server/abbenay-service.ts:1947

  • RemoveProvider currently deletes providerSecretName() unconditionally. With the new secret_name/secret_store model this can (1) try to delete env-based provider “secrets” (env var names) and (2) delete shared secrets when removing one provider that references a shared key. Consider only deleting provider-owned secrets (legacy ${PROVIDER_ID}_API_KEY or abbenay.${providerId}) and skip deletion when secret_store is "env".
            const secretName = providerSecretName(config.providers[providerId]);
            if (secretName) {
              try {
                await state.secretStore.delete(secretName);
                auditSecretChange({ key: secretName, op: 'delete', source: 'grpc-configure' });

packages/daemon/src/daemon/web/server.ts:1109

  • Same type mismatch as above: DualSecretStore.setIn only accepts "memory" | "keychain", but storeChoice.backend is typed as SecretStoreKind (includes "env"). Cast/narrow before calling setIn so this continues to typecheck and cannot accidentally treat "env" as "keychain" at runtime.
      const dual = isDualSecretStore(state.secretStore) ? state.secretStore : null;
      if (dual) {
        await dual.setIn(storeChoice.backend, parsed.data.key, parsed.data.value);
      } else {
        await state.secretStore.set(parsed.data.key, parsed.data.value);
      }

packages/daemon/src/daemon/web/server.ts:1073

  • Type mismatch: parseSecretStoreChoice() returns backend as SecretStoreKind (includes "env"), but DualSecretStore.setIn expects only "memory" | "keychain". Even though ENV is rejected for this route, the call currently passes storeChoice.backend directly. Narrow/cast after asserting it cannot be "env" to keep the type-safety (and avoid accidental behavior changes if parsing rules change).

This issue also appears on line 1104 of the same file.

      const dual = isDualSecretStore(state.secretStore) ? state.secretStore : null;
      if (dual) {
        await dual.setIn(storeChoice.backend, key, parsed.data.value);
      } else {
        await state.secretStore.set(key, parsed.data.value);

packages/daemon/src/daemon/server/abbenay-service.ts:842

  • Type mismatch: parseSecretStoreChoice() returns backend as SecretStoreKind (includes "env"), but DualSecretStore.setIn expects only "memory" | "keychain". ENV is rejected here, but the call still passes parsed.backend directly; narrow/cast after validation to keep this code type-safe and prevent "env" from ever being treated as "keychain" by DualSecretStore.

This issue also appears on line 1943 of the same file.

      const dual = isDualSecretStore(state.secretStore) ? state.secretStore : null;
      const write = dual
        ? dual.setIn(parsed.backend, key, value)
        : state.secretStore.set(key, value);
      const auditSource =

Comment thread packages/daemon/src/daemon/web/server.ts Outdated
parseSecretStoreChoice overloads so SetSecret/configure writes type as
SecretBackend; env remains configure/reference-only and CI builds again.
@cidrblock

Copy link
Copy Markdown
Collaborator Author

Follow-up issues filed:

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (2)

packages/daemon/src/daemon/web/server.ts:1554

  • When removing a provider, this deletes providerSecretName(...) from the daemon secret store unconditionally. For secret_store: "env", providerSecretName returns the env var name, so this would attempt to delete a secret-store entry with the same name (and could remove a real stored secret shared by other providers). Skip secretStore deletion when the provider is configured to use env.
        const secretName = providerSecretName(config.providers[providerId]);
        if (secretName) {
          try {
            await state.secretStore.delete(secretName);
            auditSecretChange({ key: secretName, op: 'delete', source: 'http-configure' });

packages/daemon/src/daemon/web/server.ts:1052

  • This route supports both secret_store and legacy store fields (see parsing below), but the comment only documents store. Updating the docstring avoids confusion for API consumers.
  /**
   * POST /api/secrets/:key - Set a specific secret (API key)
   * Body: { value: string, store?: "memory" | "keychain" }
   */

Comment thread packages/daemon/src/daemon/server/abbenay-service.ts Outdated
…egistry

Drop move-on-write DualSecretStore so memory and keychain stay discrete;
providers resolve only their configured backend, positioning N pluggable stores.
Copilot AI review requested due to automatic review settings August 12, 2026 16:37
@cidrblock cidrblock changed the title feat(daemon): process-lifetime in-memory secret store feat(daemon): namespaced memory + keychain secret stores Aug 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (3)

packages/daemon/src/daemon/web/api-schemas.ts:150

  • PostProviderConfigureBodySchema currently allows apiKey together with secretStore: "env", but the configure route rejects ENV for writes (parseSecretStoreChoice without allowEnv). Consider rejecting this combination at schema-validation time so HTTP clients get a clear, consistent 400 error before the route logic runs.
    if (data.apiKey && data.envVarName) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'Provide only one of apiKey or envVarName',
        path: ['apiKey'],

packages/daemon/src/daemon/web/api-schemas.ts:46

  • PostSecretBodySchema / PostSecretByKeyBodySchema accept secret_store: "env", but the HTTP secrets endpoints reject ENV via parseSecretStoreChoice (only memory|keychain are writable). This mismatch means invalid requests pass schema validation and fail later with a less-specific error; it also contradicts the endpoint docs.
const SecretStoreFieldSchema = z.enum(['memory', 'keychain', 'env']).optional();

packages/daemon/src/daemon/web/api-schemas.test.ts:190

  • This test asserts that the secrets POST schemas accept secret_store: "env", but the secrets endpoints reject ENV writes (only memory|keychain are writable). If the schemas are tightened to match the runtime contract, this expectation should be updated.
  it('accepts optional secret_store memory|keychain|env (and legacy store)', () => {
    expect(
      PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secret_store: 'memory' }).success,
    ).toBe(true);
    expect(
      PostSecretByKeyBodySchema.safeParse({ value: 'v', store: 'keychain' }).success,
    ).toBe(true);
    expect(
      PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secret_store: 'env' }).success,
    ).toBe(true);

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings August 13, 2026 13:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 33 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (4)

packages/daemon/src/daemon/web/api-schemas.ts:45

  • PostSecretBodySchema/PostSecretByKeyBodySchema currently accept secret_store: "env" (via SecretStoreFieldSchema), but the HTTP secrets endpoints reject ENV writes via parseSecretStoreChoice. This makes the request schema disagree with the actual API contract (memory|keychain only) and can mislead clients.
const SecretStoreFieldSchema = z.enum(['memory', 'keychain', 'env']).optional();

packages/daemon/src/daemon/web/server.ts:1058

  • The comment for POST /api/secrets/:key documents only store?: "memory" | "keychain", but the implementation (and request schema) uses secret_store as the preferred field and store as a legacy alias. Updating the comment avoids confusion for callers and keeps it aligned with the code.
  /**
   * POST /api/secrets/:key - Set a specific secret (API key)
   * Body: { value: string, store?: "memory" | "keychain" }
   */

packages/daemon/src/daemon/web/api-schemas.test.ts:186

  • This test asserts that PostSecretBodySchema accepts secret_store: "env", but the daemon's secrets write endpoints reject ENV (it’s a credential source, not a writable store). Keeping this expectation will codify behavior that contradicts the API contract.
    expect(
      PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secret_store: 'env' }).success,
    ).toBe(true);

packages/daemon/src/daemon/web/server.ts:1188

  • The key-status handler now supports source=memory, but the route-level comment above still documents only source=keychain|env. Update the doc comment to include memory so it matches the supported sources.
      if (source === 'keychain' || source === 'memory') {
        const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null;
        exists = registry
          ? await registry.hasIn(source, name)
          : source === 'keychain'

Copilot AI review requested due to automatic review settings August 13, 2026 13:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 33 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (4)

packages/daemon/src/core/secrets.ts:68

  • The auditSecretChange JSDoc list of allowed sources should include the new "http-configure-memory" / "grpc-configure-memory" values to match actual call sites.
 * Sources include: http-secrets | http-secrets-memory | grpc-secrets |
 * grpc-secrets-memory | http-configure | grpc-configure | core-add

packages/daemon/src/daemon/web/server.ts:1609

  • Provider delete audits all secret deletions as "http-configure" even when the owned secret was stored in the memory backend. This loses the backend distinction that ConfigureProvider already records via "http-configure-memory".
            auditSecretChange({ key: secretName, op: 'delete', source: 'http-configure' });

packages/daemon/src/daemon/server/abbenay-service.ts:2013

  • RemoveProvider audits all secret deletions as "grpc-configure" even when the owned secret was stored in the memory backend. This makes audit logs inconsistent with ConfigureProvider, which emits "grpc-configure-memory" for memory writes.
                auditSecretChange({ key: secretName, op: 'delete', source: 'grpc-configure' });

packages/daemon/src/core/secrets.ts:59

  • The SecretAuditEvent.source docstring omits the "*-configure-memory" sources that are now emitted (e.g. http-configure-memory / grpc-configure-memory). Update the list so it stays accurate for log/audit consumers.

This issue also appears on line 67 of the same file.

  /** http-secrets | http-secrets-memory | grpc-secrets | grpc-secrets-memory | http-configure | grpc-configure | core-add */

Optional store fields regenerated packages/vscode/src/proto, and Sonar
counted ~1.6k uncovered generated lines against new-code coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings August 13, 2026 13:27
Parallel CI files can clobber a shared console.warn spy, making the
short-circuit test flake. Assert the sticky loadError instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file

Comment thread packages/daemon/src/daemon/server/abbenay-service.ts
Comment thread packages/daemon/src/daemon/web/server.ts
Copilot AI review requested due to automatic review settings August 13, 2026 13:33
Clients could request memory while a plain store silently persisted to
keychain and audited as memory. Require a namespaced registry for memory
on gRPC/HTTP secrets and configure paths.

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (3)

packages/daemon/src/daemon/web/api-schemas.ts:49

  • The secrets write body schemas allow secret_store: "env", but the corresponding HTTP routes call parseSecretStoreChoice(...) without allowEnv and will always reject ENV writes (400). This makes the schema contract inconsistent with runtime behavior and the PR summary (“env rejected — credential source only”). Consider restricting the secrets write schemas to only memory|keychain (keep env for provider config/reference).
const SecretStoreFieldSchema = z.enum(['memory', 'keychain', 'env']).optional();

export const PostSecretByKeyBodySchema = z
  .object({
    value: z.string().min(1),

packages/daemon/src/daemon/web/api-schemas.test.ts:190

  • This test currently asserts that secret write bodies accept secret_store: "env", but the secrets routes intentionally reject ENV writes (env is reference-only). Once the schema is tightened to memory|keychain, this expectation should flip to false to match the intended contract.
  it('accepts optional secret_store memory|keychain|env (and legacy store)', () => {
    expect(
      PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secret_store: 'memory' }).success,
    ).toBe(true);
    expect(
      PostSecretByKeyBodySchema.safeParse({ value: 'v', store: 'keychain' }).success,
    ).toBe(true);
    expect(
      PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secret_store: 'env' }).success,
    ).toBe(true);
    expect(

packages/daemon/src/core/secrets.ts:60

  • auditSecretChange source docs don’t mention the newly introduced *-configure-memory sources, even though web/gRPC provider configure now emit http-configure-memory / grpc-configure-memory. Updating the inline docs helps keep audit-source semantics discoverable/consistent.
export interface SecretAuditEvent {
  /** Secret key name only — never the value */
  key: string;
  op: 'set' | 'delete';
  /** http-secrets | http-secrets-memory | grpc-secrets | grpc-secrets-memory | http-configure | grpc-configure | core-add */
  source: string;

Copilot AI review requested due to automatic review settings August 13, 2026 13:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py: Generated file
Suppressed comments (1)

packages/daemon/src/daemon/web/server.ts:1502

  • When referencing an existing secretName (no apiKey), an invalid secretStore value currently falls through to the default keychain lookup branch. That means requests like { secretName: "X", secretStore: "vault" } are silently treated as keychain instead of failing closed with a 400, which is inconsistent with the rest of the API (and with the gRPC handler). Add explicit validation for unsupported secretStore values before the env/memory/keychain branching.
        const storeRaw = secretStoreBody;
        if (storeRaw === 'env') {

@sonarqubecloud

Copy link
Copy Markdown

@Hrithik-Gavankar
Hrithik-Gavankar self-requested a review August 13, 2026 14:21

@Hrithik-Gavankar Hrithik-Gavankar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes LGTM!

@cidrblock
cidrblock merged commit 897d497 into redhat-developer:main Aug 13, 2026
14 checks passed
cidrblock added a commit to cidrblock/abbenay that referenced this pull request Aug 13, 2026
Hard-break the miss from redhat-developer#113 — no deprecated snake_case or bare store
fields on secrets request bodies or DELETE query.

Co-authored-by: Cursor <cursoragent@cursor.com>
cidrblock added a commit that referenced this pull request Aug 13, 2026
…retStore

fix(daemon): camelCase HTTP secretStore (missed in #113)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants