Skip to content

feat(vault): enforce allowedAgents at deploy time instead of documenting it - #659

Closed
ginccc wants to merge 2 commits into
mainfrom
feat/vault-allowed-agents-enforcement
Closed

feat(vault): enforce allowedAgents at deploy time instead of documenting it#659
ginccc wants to merge 2 commits into
mainfrom
feat/vault-allowed-agents-enforcement

Conversation

@ginccc

@ginccc ginccc commented Aug 9, 2026

Copy link
Copy Markdown
Member

Closes the second open item from the Wave-R follow-up review.

SecretMetadata.allowedAgents was documented as "for visibility only — enforcement is via configuration authorship, not runtime resolution". That access model assumes a human admin authors agent configurations. create_sub_agent lets an LLM author one — so an operator who scoped a secret to a single agent got no enforcement at all. The field was decorative.

Why deployment time, and not resolution time

This is the load-bearing decision, so it's worth stating why the obvious place is the wrong one.

SecretResolver cannot do it soundly. It sees only a string — no agent identity — and its ~12 call sites span ApiCallExecutor, ChatModelRegistry, A2AToolProviderManager, McpToolProviderManager, EmbeddingModelFactory, EmbeddingStoreFactory and ChannelTargetRouter. Several legitimately run outside any conversation, and AgentSigningService bypasses the resolver entirely.

The model cache would launder the check. ChatModelRegistry caches on ModelCacheKey(type, unresolvedParams). Two agents sharing a config share a cache entry, so a check inside resolveSecrets runs for whichever agent builds the model first and is silently skipped for every other one — enforcement that looks real and is not, which is exactly what I declined to ship when I first reported this. Making it sound needs the agent identity in the cache key plus plumbing through six getOrCreate call sites, several of them background services (SummarizationService, ToolResponseTruncator) with no agent at all.

The binding between an agent and a secret is established in the agent's configuration. That is where it can be checked completely, once, and with no cache in the way — next to the existing deploy-time lintInertHitlConfig, which is the same pattern.

What it does

VaultGrantChecker walks the agent's workflows → llm / apicalls / mcpcalls configs, serializes each and scans for ${vault:...} references, then verifies each against allowedAgents.

The scan serializes rather than enumerating known credential fields. Enumeration is how this kind of check rots: someone adds a new credential field and the scanner silently stops covering it. Pinned by a test that plants a reference in a field named someFutureCredentialField.

Rollout is warn-first

eddi.vault.grant-enforcement = off | warn (default) | enforce.

Warn is deliberate. The field has never been enforced, so any non-wildcard value in an existing deployment is untested configuration — blocking on it during an upgrade could take agents down for a policy nobody has yet had a chance to verify. Operators switch to enforce once the warnings are clean.

Uncertainty never becomes a violation

Unreadable metadata, a disabled vault, an unreadable workflow, and absent / empty / wildcard grants all allow. A deployment gate that fires on a transient store failure is worse than the hole it closes. Every AgentSetupService-vaulted key carries ["*"], so existing deployments see no change.

What it does not do

An agent already deployed before its grant was narrowed keeps resolving until it is redeployed. This is a deploy-time gate, not a revocation mechanism — stated in the class Javadoc rather than left for someone to discover.

Testing

+9 tests; 274 green across the vault, deployment and setup suites.

Worth noting: one test initially passed vacuously. The fixture used toy ids (w1, l1), and RestUtilities.extractResourceId requires ≥18 hex characters — it returns a null id for anything shorter, so the scanner walked nothing and found nothing. The fixture now uses ObjectId-shaped ids, and the failure it produced first is what proved the scanner actually works.

Summary by CodeRabbit

  • New Features

    • Added deployment-time validation for vault access permissions.
    • Added configurable enforcement modes: off, warn, and enforce, with warn as the default.
    • Vault references are detected across workflows and supported extension configurations.
  • Bug Fixes

    • Prevented deployments in enforce mode when required vault access is not granted.
    • Added permissive handling for missing, unreadable, malformed, or unsupported configuration data.
  • Documentation

    • Documented enforcement behavior, rollout modes, and deployment retry handling.

…ing it

SecretMetadata.allowedAgents was 'for visibility only — enforcement is via
configuration authorship'. That model assumes a human admin authors agent
configs; create_sub_agent lets an LLM author one, so scoping a secret to one
agent bought nothing.

Enforced at deployment, not resolution, and that is the load-bearing decision:
SecretResolver sees only a string and several of its call sites have no agent at
all, while ChatModelRegistry caches models keyed on the UNRESOLVED parameters —
so a check behind that cache runs for whichever agent built the model first and
is skipped for every other one. That would be enforcement that looks real and is
not. The agent/secret binding lives in the agent's configuration, so it is
checked there: completely, once, with no cache in the way.

VaultGrantChecker walks workflows -> llm/apicalls/mcpcalls configs, serializes
each and scans for vault references rather than enumerating known credential
fields, then checks each against allowedAgents.

eddi.vault.grant-enforcement = off|warn|enforce, defaulting to warn: the field
has never been enforced, so non-wildcard values in existing deployments are
untested configuration and blocking on upgrade could take agents down.

Uncertainty never becomes a violation — unreadable metadata, disabled vault,
unreadable workflow and wildcard/empty grants all allow.

+9 tests.
@ginccc
ginccc requested a review from rolandpickl as a code owner August 9, 2026 19:27
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 182e1a3c-151b-46a3-bc15-5071e9000ee1

📥 Commits

Reviewing files that changed from the base of the PR and between 7e7ebe7 and 2dfd8d2.

📒 Files selected for processing (3)
  • src/main/java/ai/labs/eddi/engine/runtime/internal/AgentDeploymentManagement.java
  • src/test/java/ai/labs/eddi/engine/runtime/internal/GrantEnforcementModeTest.java
  • src/test/java/ai/labs/eddi/secrets/VaultGrantCheckerTest.java

📝 Walkthrough

Walkthrough

Vault grant enforcement now checks workflow vault references before agent deployment. off, warn, and enforce modes control deployment behavior. The checker scans supported extension configurations and fails open for unavailable or unreadable data.

Changes

Vault grant deployment enforcement

Layer / File(s) Summary
Vault reference validation
src/main/java/ai/labs/eddi/secrets/VaultGrantChecker.java
VaultGrantChecker loads workflow extension configurations, scans serialized data for vault references, and compares references with allowedAgents.
Deployment enforcement gate
src/main/java/ai/labs/eddi/engine/runtime/internal/AgentDeploymentManagement.java, src/test/java/ai/labs/eddi/engine/runtime/internal/GrantEnforcementModeTest.java
Deployment uses off, warn, or enforce modes. Enforced violations block deployment and remain eligible for later polling retries. Mode parsing validates supported values and defaults blank or absent settings to WARN.
Checker behavior validation
src/test/java/ai/labs/eddi/secrets/VaultGrantCheckerTest.java, docs/changelog.md
Tests cover scoped, wildcard, empty, and null grants; fail-open cases; supported extension types; and arbitrary serialized credential fields. The changelog documents the behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentDeploymentManagement
  participant VaultGrantChecker
  participant WorkflowStores
  participant SecretProvider
  AgentDeploymentManagement->>VaultGrantChecker: Validate agent vault references
  VaultGrantChecker->>WorkflowStores: Load workflows and extension configurations
  VaultGrantChecker->>VaultGrantChecker: Scan serialized configurations
  VaultGrantChecker->>SecretProvider: Read grant metadata
  SecretProvider-->>VaultGrantChecker: Return metadata or read failure
  VaultGrantChecker-->>AgentDeploymentManagement: Return ungranted references
  AgentDeploymentManagement->>AgentDeploymentManagement: Apply enforcement mode
Loading

Possibly related PRs

  • labsai/EDDI#418: Both changes handle ${vault:...} references and vault-reference documentation.
  • labsai/EDDI#535: Both changes use the same vault APIs and modify vault-reference handling.
  • labsai/EDDI#648: Both changes modify deployment lifecycle handling in AgentDeploymentManagement.

Suggested reviewers: rolandpickl

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enforcing vault allowedAgents during deployment.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/vault-allowed-agents-enforcement

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 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
`@src/main/java/ai/labs/eddi/engine/runtime/internal/AgentDeploymentManagement.java`:
- Around line 198-204: Ensure every agent deployment path checks vault grants
before deployment, not only checkDeployments. Update the shared deployment flow
or each caller around agentFactory.deployAgent, including the paths near lines
413 and 462, to invoke vaultGrantsSatisfied and prevent deployment when
enforcement rejects ungranted references.
- Around line 266-293: Validate vaultGrantEnforcement during startup
configuration validation, accepting only case-insensitive “off”, “warn”, or
“enforce” values and failing validation for anything else. Update the relevant
startup validation path rather than relying on vaultGrantsSatisfied’s fallback
behavior; preserve the existing enforcement semantics for valid values.

In `@src/test/java/ai/labs/eddi/secrets/VaultGrantCheckerTest.java`:
- Around line 51-63: Extend the VaultGrantChecker test suite around setUp and
the existing workflow-step cases with separate API-call and MCP-call scenarios.
Configure each step using the mocked IApiCallsStore or IMcpCallsStore, place
VAULT_REF in an arbitrary credential field, and assert that an ungranted agent
reports the reference as a violation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57e19514-b01f-4911-99f4-75cc0229cb93

📥 Commits

Reviewing files that changed from the base of the PR and between d5294a6 and 7e7ebe7.

📒 Files selected for processing (4)
  • docs/changelog.md
  • src/main/java/ai/labs/eddi/engine/runtime/internal/AgentDeploymentManagement.java
  • src/main/java/ai/labs/eddi/secrets/VaultGrantChecker.java
  • src/test/java/ai/labs/eddi/secrets/VaultGrantCheckerTest.java

Comment thread src/main/java/ai/labs/eddi/engine/runtime/internal/AgentDeploymentManagement.java Outdated
Comment thread src/test/java/ai/labs/eddi/secrets/VaultGrantCheckerTest.java Outdated
… enforcement values

CodeRabbit on #659, three findings, all valid:

- The gate guarded checkDeployments alone. manageAgentDeployments' latest-version
  redeploy and manageDeploymentOfOldAgent called agentFactory.deployAgent
  directly, so in enforce mode an agent with ungranted references was blocked by
  one path and deployed by another a day later. A gate with a way round it is not
  a gate; there is now a single deployIfGranted entry point instead of three call
  sites that each have to remember.
- Any value other than off/enforce silently meant warn, so the plausible typo
  'enforced' produced a security control that was off while appearing on. Parsed
  strictly into an enum and validated in @PostConstruct, so an unusable value
  fails startup with the valid values named — the discipline
  Deployment.Environment.parseStrict already applies.
- The test suite configured only LLM steps; IApiCallsStore and IMcpCallsStore
  were mocked and never reached, so a regression dropping either branch would
  have passed. Both are now covered.

+6 tests.
@aisabella-ai
aisabella-ai self-requested a review August 10, 2026 17:40
@ginccc

ginccc commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

Superseded by a rebuild on current main — closing without merging.

main moved substantially while this was in review (#648#651 landed). This branch was based on the older main and had become CONFLICTING, which on this repo means ci.yml stops running entirely while CodeQL/Codacy stay green — so its checks were no longer meaningful.

The content of this PR is not lost. It was verified as still-missing against current main and rebuilt there:

Every review finding raised here — including all of CodeRabbit's — is carried into the replacement PRs along with its test. The discussion here remains the record of the reasoning.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants