Skip to content

feat(agent-vault): sessions on behalf of users, access bundles, delegations and proxy descriptions - #66

Merged
scott-ray-wilson merged 15 commits into
mainfrom
AGE2-288
Nov 4, 2026
Merged

scott-ray-wilson merged 15 commits into
mainfrom
AGE2-288

Conversation

@scott-ray-wilson

@scott-ray-wilson scott-ray-wilson commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Context

Infisical/infisical#8773 adds sessions on behalf of users: a machine identity with a delegation names a user in onBehalfOf when it creates a session, and the proxy then uses that user's personal values. This adds it to the SDK, along with the access bundle and delegation methods needed to set it up. It also adds the service-hosts rename and inline services from Infisical/infisical#9064.

Sessions on behalf of users (client.agent_vault.sessions)

  • Create: create takes on_behalf_of_user_email or on_behalf_of_user_id, and sends it as onBehalfOf: {"userEmail": ...} or {"userId": ...}. Passing both raises a ValueError before any request. An empty string is sent, not dropped, so the server rejects it instead of the SDK quietly creating the session for the machine identity.
  • TTL default: ttl now defaults to None and is left out of the request, so the server picks the default: 7d, or 1h for a session on behalf of a user. Released servers also default a missing ttl to 7d, so existing callers get the same sessions.
  • Models: AgentVaultSession.onBehalfOf is the user, as an AgentVaultSessionActor, since the API shapes it like a user actor. It's None for the machine identity's own session, and its id is None once the user is deleted. get, list, update and revoke all return it. It's the last field, so positional construction of the model doesn't change.
  • Docstrings: list's search and update's label rule now match the API docs in #8773.

Access bundles (client.agent_vault.access_bundles)

  • Bundles: list, get, create, update and delete. list takes on_behalf_of_user_email or on_behalf_of_user_id, to list the bundles a session on that user's behalf could use. It sends requires_setup and granted as "true" or "false", the only values the API reads, where requests would send "True". update removes the description when you pass an empty string, since the server stores a blank description as null. create isn't retried once the request may have reached the server, like the other creates.
  • Grants: list_grantees, list_available_grantees, grant and revoke, for the API's /members routes. grant and revoke return the grantees they changed and the ones they skipped.
  • Services: get returns the bundle's services. The SDK doesn't manage services, so a service's nested settings, such as its credential, stay as the API returns them.

Delegations (client.agent_vault.delegations)

  • list, get, set, delete, add_subjects, remove_subjects and list_available_subjects, named after the API reference pages. remove_subjects returns None once it takes out the last user or group, since the API then removes the delegation.

Proxy descriptions (client.agent_vault.proxies, from Infisical/infisical#8963)

  • create and update take description, and update removes it when you pass an empty string, as for access bundles. The new arguments come last, so positional calls written for 1.1.0 still work.
  • AgentVaultProxy.description is set for members too, and list's search also matches descriptions. delete still returns only id and name.
  • Breaking: proxies.update no longer takes clear_allowed_hosts, which shipped in 1.1.0. To remove every allowed host, pass allowed_hosts=[] or "", and the SDK sends null, since the API rejects an empty host pattern. proxies.create also treats an empty list or string as no hosts. This leaves one way to clear each field: an empty value. Agent Vault has no adoption yet, so there's no compatibility alias.

Service hosts and inline services (from Infisical/infisical#9064)

  • Traffic policy: AgentVaultTrafficPolicy.SERVICE_HOSTS is the new name of bundle-hosts. BUNDLE_HOSTS, which 1.1.0 shipped, stays as another name for the same member, so code that passes it or compares a proxy's policy with it keeps working. The SDK sends either name as bundle-hosts, which every Infisical version accepts, and reports a proxy's policy as service-hosts, even from a version before the rename.
  • Inline services: sessions.create takes services, a list of dicts with the API's field names, and access_bundle is now optional. services comes last, so positional calls written for 1.1.0 still work. Sessions return inline services as AgentVaultInlineService objects without secrets. get, list and update give a session from a version without inline services an empty list, and revoke leaves them as None, like accessBundles, since the API leaves both out.
  • Models: AgentVaultService.allowedTools and AgentVaultGrantee.isOrgMembershipPending, which the API already returns.
  • Behavior change: AgentVaultTrafficPolicy.BUNDLE_HOSTS.value is now "service-hosts". That only matters to code that sends .value to a pre-rename server without going through the SDK, or compares it with a saved "bundle-hosts" string.

One access bundle per session (#67, merged into this branch from main)

  • sessions.create sends accessBundle, and AgentVaultSession and CreatedAgentVaultSession have accessBundle instead of accessBundles, beside this PR's onBehalfOf and services. The merge's conflicts were in the session models, sessions.create and the tests, and feat(agent-vault)!: sessions carry one accessBundle #67's tests are kept.

IDs, grants, deletes and grantee filters (from the reviews of this PR and of the same methods in Infisical/node-sdk-v2#46)

  • IDs: each ID goes into the path as one encoded segment, and an empty ID, ., .. or None raises a ValueError before any request. Before, an ID such as .. or a/../.. reached the route that removes members from Agent Vault, which takes the same body as access_bundles.revoke.
  • Grants, revokes and deletes: access_bundles.grant uses post_once, and access_bundles.revoke, the delete methods of proxies, access bundles and delegations, and delegations.remove_subjects use a new delete_once, so none of them is retried once the request may have reached the server. Grants and revokes say which grantees changed, so after a lost response, a retry would report each one as skipped, and a retried delete would get a 404 for a delete that worked.
  • Parsing: one helper parses missingAccessBundles for the delegation, user and group models.
  • Grantee filters: list_grantees and list_available_grantees take actor_type, to list only users, machine identities or groups.

Other changes

  • PUT requests: InfisicalRequests.put, for delegations.set. It retries like patch, since a PUT replaces the delegation.
  • Enums: AgentVaultMemberType and AgentVaultDelegationScope, exported from infisical_sdk.
  • Errors: APIError, which has status_code, is exported from infisical_sdk next to InfisicalError, so code can catch it without importing from infisical_sdk.infisical_requests.

For the reviewer

  • Older servers: an Infisical version without #8773 drops onBehalfOf from session creation, because zod strips unknown keys. It then creates the session for the machine identity, with a 7d TTL, and the proxy uses the identity's personal values. The create response doesn't include onBehalfOf, so the SDK can't detect this. As the review thread says, that's fine while Agent Vault has no adoption, and this merges only after #8773 is deployed, so neither the docstring nor the docs mention it.
  • Older servers and inline services: a version without feat(agent-vault): inline services on sessions infisical#9064 strips services the same way. If the request also names an access bundle, it creates the session with only the bundle, and the SDK doesn't detect it.
  • Naming: grants use grant, revoke and list_grantees rather than the REST path's "members", so they don't read as Agent Vault membership. The delegation methods keep the API reference's names, including "subjects" for users and groups.
  • Not included: managing an access bundle's services, personal values and skills, the rest of the access bundle API. Services alone carry large nested credential settings.

Release this as infisicalsdk 1.2.0, a minor release, since removing clear_allowed_hosts breaks code written for 1.1.0, as does #67's accessBundle. The docs are in Infisical/infisical#9067, which says the new methods need 1.2.0. Merge them after this is released.

Steps to verify the change

  1. Run python -m pytest tests.
  2. Run Infisical from main, and log in with the SDK as an Agent Vault admin. Create an access bundle with access_bundles.create, grant it to a machine identity and a user with access_bundles.grant, and give the machine identity a delegation for the user with delegations.set.
  3. Log in as the machine identity. access_bundles.list(on_behalf_of_user_email="<user-email>") lists the bundle, and sessions.create(access_bundle="<bundle>", on_behalf_of_user_email="<user-email>") creates a session whose onBehalfOf is the user and that expires in about an hour.

Testing done

  • Unit: 93 tests pass on Python 3.8 through 3.12, including feat(agent-vault)!: sessions carry one accessBundle #67's, and 75 of them are new or rewritten in this PR. With each rule broken on purpose, only the test written for it fails:

    • sessions: dropping an empty email, not parsing onBehalfOf, a 7d default, and no check for both arguments
    • access bundles and delegations: booleans sent as Python's True and False, a retried bundle create, a revoke without a body, a null delegation parsed as one, and no email-or-ID check in list
    • descriptions: a proxy create that leaves the description out, and proxy and bundle updates that drop an empty description
    • allowed hosts: an update that sends no null for empty hosts, and blank strings not treated as empty

    For service hosts and inline services, 15 of 16 deliberate breaks fail a test, and the 16th was dead code, which is now removed. For the IDs, grants and grantee filters, each of 10 deliberate breaks fails a test. The flake8 checks that CI enforces pass.

  • Live, sessions on behalf of users: 33 checks on an isolated stack at #8773's e4bb7af1ce, and 35 on a local dev instance at b1320d014d, the head that merged, in a throwaway org with a seeded user. They covered creating by email, by ID and with an uppercase email, the 1h, 7d, 15m and never TTLs, onBehalfOf from every read, search by the user, the admin's and the user's lists, a 404 when the user reads a session, and each refusal.

  • Live, access bundles and delegations: 53 checks on the same local dev instance, whose access bundle and delegation code matches main, in a throwaway org with two seeded users, one in Agent Vault only through a group. They covered:

    • every new method as an admin, including the creator's automatic grant on create, and the granted, search and sort options of list
    • grants to users, machine identities and groups, with repeats skipped
    • each delegation change: set with each scope, adding and removing a group, removing the last user, add_subjects creating a delegation, and delete
    • missingAccessBundles, and the refusal to name a user who's in Agent Vault only through a group
    • what a Member can't do: a machine identity creating bundles or listing delegations, and a user listing grantees
    • a session on behalf of a user set up with SDK calls only

    The server logged no errors.

  • Live, proxy descriptions: 19 checks on an isolated stack at main (20919824a1, with #8963). They covered setting and changing a description, removing it with a blank one (stored as null), search by description, a line break rejected, the description in rotate_token, revoke and a member's list, positional create calls from 1.1.0, and a short pass over access bundles, delegations and a session on behalf of a user on the same code. The server logged no errors.

  • Live, remaining paths: 23 checks on an isolated stack at main (101d093658) covered what the runs above left to unit tests: requires_setup for a member and on their behalf, before and after the member set a personal value; get on a bundle with a real service; a session whose user is then deleted (onBehalfOf.id becomes None, the session reads as revoked, and its label can't change); and search and paging on the new list methods. Only the retry behavior is left to unit tests, since checking it needs injected network failures.

  • Live, allowed hosts: 7 checks on the local dev instance in a throwaway org. Each of [], "" and a blank string removed every host and kept the traffic policy, an update without allowed_hosts left them alone, and create with an empty list or string made a proxy with no hosts.

  • Live, service hosts and inline services: 53 checks on a local dev instance running feat(agent-vault): inline services on sessions infisical#9140, which adds inline services, with two throwaway machine identities. They covered every method again, every spelling of the traffic policy, positional create calls written for 1.1.0, inline services alone, with an access bundle and on behalf of a user who's a member, the 403, 422 and 400 refusals, and that revoking a session deletes its inline services' secrets. Teardown left nothing behind. After merging feat(agent-vault)!: sessions carry one accessBundle #67, all of them passed again against main with improvement(agent-vault): sessions take and return one accessBundle infisical#9099, plus a new check that a session keeps its access bundle's name, with id None, once the bundle is deleted.

  • Live, IDs, grants and grantee filters: 13 checks on the same local instance. They covered refused IDs, an ID with slashes or encoded dots reaching the session route as one segment, actor_type on live grantees, and, against a local server that drops the connection, one request each for grant and revoke, where proxies.update sends five. These passed again after merging feat(agent-vault)!: sessions carry one accessBundle #67.

Type

  • Fix
  • Feature
  • Improvement
  • Breaking
  • Docs
  • Chore

Checklist

  • Title follows the conventional commit format: type(scope): short description (scope is optional, e.g., fix: prevent crash on sync or fix(api): handle null response).
  • Tested locally
  • Updated docs (if needed)
  • Updated AGENTS.md files (if needed)
  • Read the contributing guide

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear

linear Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

AGE2-288

AGE2-292

@greptile-apps

greptile-apps Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5 Tier: plus

[High impact] The PR appears safe to merge with the documented server rollout and compatibility decisions.

Summary

Adds sessions on behalf of users, access bundles, delegations, proxy descriptions, and inline services.

  • Sessions can act for a user and carry inline services.
  • Access bundles can be managed and granted to Agent Vault members.
  • Delegations let machine identities act for selected users or groups.
  • Proxies accept descriptions and use the service-hosts policy name.
  • The SDK adds request helpers for delegation updates and one-shot deletes.

Reviews (13) · Last reviewed commit: "refactor(agent-vault): parse missingAcce..." · Reviewed by Greptile

@claude claude 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.

Beyond the inline finding, I checked that create() depends on post_once in infisical_sdk/infisical_requests.py to leave out None-valued keys. It does: it filters top-level keys with is not None, so a None ttl, label or onBehalfOf is left out of the request, and an empty-string email is still sent.

Extended reasoning...

The change adds on-behalf-of-user session creation to the Agent Vault sessions resource, an optional onBehalfOf field on the AgentVaultSession model, and unit tests. It touches authorization-adjacent behavior: which principal's values a session uses. The ruled-out item is the request layer's None filtering, which matches what the new tests and the PR description assume.

Comment thread infisical_sdk/resources/agent_vault.py Resolved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scott-ray-wilson scott-ray-wilson changed the title feat(agent-vault): create sessions on behalf of users feat(agent-vault): sessions on behalf of users, access bundles and delegations Nov 2, 2026
scott-ray-wilson and others added 2 commits November 2, 2026 09:47
…of users

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scott-ray-wilson scott-ray-wilson changed the title feat(agent-vault): sessions on behalf of users, access bundles and delegations feat(agent-vault): sessions on behalf of users, access bundles, delegations and proxy descriptions Nov 2, 2026
scott-ray-wilson and others added 3 commits November 2, 2026 14:03
…clear_description

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ad of clear_allowed_hosts

BREAKING CHANGE: proxies.update no longer takes clear_allowed_hosts. Pass an
empty list or string as allowed_hosts to remove every allowed host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Infisical renamed the bundle-hosts traffic policy to service-hosts and added
inline services, which are services sent with a session instead of added to
an access bundle (Infisical/infisical#9064).

- AgentVaultTrafficPolicy gets SERVICE_HOSTS. BUNDLE_HOSTS, which 1.1.0
  shipped, stays as another name for the same member, so code that passes it
  or compares a proxy's policy with it keeps working. The SDK sends either
  name as bundle-hosts, which every Infisical version accepts, and reports a
  proxy's policy as service-hosts.
- sessions.create takes services, added last so positional calls from 1.1.0
  still work, and access_bundle is optional. Sessions return their inline
  services as AgentVaultInlineService objects without secrets, and get, list
  and update give a session from a version without inline services an empty
  list.
- AgentVaultService has allowedTools, and AgentVaultGrantee has
  isOrgMembershipPending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread infisical_sdk/resources/agent_vault.py Resolved
scott-ray-wilson and others added 2 commits November 3, 2026 18:46
…of its tools

Infisical refuses a non-empty allowedTools on a service sent with a session, so
the services docstring of sessions.create now lists that limit with the others,
and says to add the MCP server to an access bundle to limit its tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r grantees by type

From the review of the same methods in Infisical/node-sdk-v2#46:

- Each ID goes into the path as one encoded segment, and an empty ID, ".",
  ".." or None raises a ValueError before any request. Before, an ID such as
  ".." or "a/../.." reached the route that removes members from Agent Vault,
  which takes the same body as access_bundles.revoke.
- access_bundles.grant uses post_once, and revoke a new delete_once, so
  neither is retried once the request may have reached the server. Their
  responses say which grantees changed, so after a lost response, a retry
  would report each one as skipped.
- list_grantees and list_available_grantees take actor_type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scott-ray-wilson and others added 3 commits November 4, 2026 10:57
Brings in #67, so sessions carry one accessBundle instead of an accessBundles
list. The conflicts were in the session models, sessions.create and the tests:
this branch's onBehalfOf and inline services now sit beside #67's
accessBundle, sessions.create sends accessBundle with services, and #67's
tests are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
APIError, which carries status_code, was only importable from
infisical_sdk.infisical_requests, so `from infisical_sdk import APIError`
failed. It's exported next to InfisicalError now, as the Node.js SDK exports
its error classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…change

Taking users out of a delegation or deleting it doesn't revoke the sessions
created on their behalf: the proxy refuses them until a delegation covers the
users again. The delete and remove_subjects docstrings now say so, and that
revoking the sessions ends them for good.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread infisical_sdk/resources/agent_vault.py Resolved
Comment thread infisical_sdk/api_types.py Resolved
scott-ray-wilson and others added 2 commits November 4, 2026 14:21
proxies.delete, access_bundles.delete, delegations.delete and
delegations.remove_subjects use delete_once, like grants and revokes. After a
lost response, a retry got a 404 for a delete that worked, and removing the
last user or group raised instead of returning None.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The delegation, delegation user and delegation group models each parsed
missingAccessBundles the same way. One helper does it for all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scott-ray-wilson
scott-ray-wilson merged commit 64e29d0 into main Nov 4, 2026
13 checks passed
sansynx pushed a commit to sansynx/node-sdk-v2 that referenced this pull request Nov 6, 2026
…and delegations

Adds sdk.agentVault() with proxies, sessions, accessBundles and delegations,
at parity with the Python SDK as of Infisical/python-sdk-official#66,
including sessions on behalf of users and proxy descriptions. Creates and
proxy token rotations use postOnce.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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