Repository navigation
feat(agent-vault): sessions on behalf of users, access bundles, delegations and proxy descriptions - #66
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[High impact] The PR appears safe to merge with the documented server rollout and compatibility decisions.
|
There was a problem hiding this comment.
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.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of users Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
…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>
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>
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>
…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>
Context
Infisical/infisical#8773 adds sessions on behalf of users: a machine identity with a delegation names a user in
onBehalfOfwhen 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)createtakeson_behalf_of_user_emailoron_behalf_of_user_id, and sends it asonBehalfOf: {"userEmail": ...}or{"userId": ...}. Passing both raises aValueErrorbefore 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.ttlnow defaults toNoneand 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 missingttlto 7d, so existing callers get the same sessions.AgentVaultSession.onBehalfOfis the user, as anAgentVaultSessionActor, since the API shapes it like a user actor. It'sNonefor the machine identity's own session, and itsidisNoneonce the user is deleted.get,list,updateandrevokeall return it. It's the last field, so positional construction of the model doesn't change.list'ssearchandupdate's label rule now match the API docs in #8773.Access bundles (
client.agent_vault.access_bundles)list,get,create,updateanddelete.listtakeson_behalf_of_user_emailoron_behalf_of_user_id, to list the bundles a session on that user's behalf could use. It sendsrequires_setupandgrantedas"true"or"false", the only values the API reads, where requests would send"True".updateremoves the description when you pass an empty string, since the server stores a blank description asnull.createisn't retried once the request may have reached the server, like the other creates.list_grantees,list_available_grantees,grantandrevoke, for the API's/membersroutes.grantandrevokereturn the grantees they changed and the ones they skipped.getreturns 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_subjectsandlist_available_subjects, named after the API reference pages.remove_subjectsreturnsNoneonce 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)createandupdatetakedescription, andupdateremoves 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.descriptionis set for members too, andlist'ssearchalso matches descriptions.deletestill returns onlyidandname.proxies.updateno longer takesclear_allowed_hosts, which shipped in 1.1.0. To remove every allowed host, passallowed_hosts=[]or"", and the SDK sendsnull, since the API rejects an empty host pattern.proxies.createalso 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)
AgentVaultTrafficPolicy.SERVICE_HOSTSis the new name ofbundle-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 asbundle-hosts, which every Infisical version accepts, and reports a proxy's policy asservice-hosts, even from a version before the rename.sessions.createtakesservices, a list of dicts with the API's field names, andaccess_bundleis now optional.servicescomes last, so positional calls written for 1.1.0 still work. Sessions return inline services asAgentVaultInlineServiceobjects without secrets.get,listandupdategive a session from a version without inline services an empty list, andrevokeleaves them asNone, likeaccessBundles, since the API leaves both out.AgentVaultService.allowedToolsandAgentVaultGrantee.isOrgMembershipPending, which the API already returns.AgentVaultTrafficPolicy.BUNDLE_HOSTS.valueis now"service-hosts". That only matters to code that sends.valueto 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.createsendsaccessBundle, andAgentVaultSessionandCreatedAgentVaultSessionhaveaccessBundleinstead ofaccessBundles, beside this PR'sonBehalfOfandservices. The merge's conflicts were in the session models,sessions.createand 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)
.,..orNoneraises aValueErrorbefore any request. Before, an ID such as..ora/../..reached the route that removes members from Agent Vault, which takes the same body asaccess_bundles.revoke.access_bundles.grantusespost_once, andaccess_bundles.revoke, thedeletemethods of proxies, access bundles and delegations, anddelegations.remove_subjectsuse a newdelete_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.missingAccessBundlesfor the delegation, user and group models.list_granteesandlist_available_granteestakeactor_type, to list only users, machine identities or groups.Other changes
InfisicalRequests.put, fordelegations.set. It retries likepatch, since a PUT replaces the delegation.AgentVaultMemberTypeandAgentVaultDelegationScope, exported frominfisical_sdk.APIError, which hasstatus_code, is exported frominfisical_sdknext toInfisicalError, so code can catch it without importing frominfisical_sdk.infisical_requests.For the reviewer
onBehalfOffrom 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 includeonBehalfOf, 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.servicesthe 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.grant,revokeandlist_granteesrather 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.Release this as
infisicalsdk1.2.0, a minor release, since removingclear_allowed_hostsbreaks code written for 1.1.0, as does #67'saccessBundle. 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
python -m pytest tests.main, and log in with the SDK as an Agent Vault admin. Create an access bundle withaccess_bundles.create, grant it to a machine identity and a user withaccess_bundles.grant, and give the machine identity a delegation for the user withdelegations.set.access_bundles.list(on_behalf_of_user_email="<user-email>")lists the bundle, andsessions.create(access_bundle="<bundle>", on_behalf_of_user_email="<user-email>")creates a session whoseonBehalfOfis 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:
onBehalfOf, a7ddefault, and no check for both argumentsTrueandFalse, a retried bundle create, a revoke without a body, anulldelegation parsed as one, and no email-or-ID check inlistnullfor empty hosts, and blank strings not treated as emptyFor 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 atb1320d014d, 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 andneverTTLs,onBehalfOffrom 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:create, and thegranted, search and sort options oflistsetwith each scope, adding and removing a group, removing the last user,add_subjectscreating a delegation, anddeletemissingAccessBundles, and the refusal to name a user who's in Agent Vault only through a groupThe 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 asnull), search by description, a line break rejected, the description inrotate_token,revokeand a member's list, positionalcreatecalls 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_setupfor a member and on their behalf, before and after the member set a personal value;geton a bundle with a real service; a session whose user is then deleted (onBehalfOf.idbecomesNone, 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 withoutallowed_hostsleft them alone, andcreatewith 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
createcalls 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 againstmainwith improvement(agent-vault): sessions take and return one accessBundle infisical#9099, plus a new check that a session keeps its access bundle's name, withidNone, 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_typeon live grantees, and, against a local server that drops the connection, one request each forgrantandrevoke, whereproxies.updatesends five. These passed again after merging feat(agent-vault)!: sessions carry one accessBundle #67.Type
Checklist
type(scope): short description(scope is optional, e.g.,fix: prevent crash on syncorfix(api): handle null response).🤖 Generated with Claude Code