Identity in AI generated apps, agents and workflows
How Major handles identity and authorization for AI-generated apps, agents, and workflows: scoped deployment identities, a connector proxy, live revokes, and the tradeoffs.

Identity is a topic we spent very intentional cycles refining early on at Major. This has paid dividends as we moved from just apps to apps, agents and workflows. Our original posture has remained very resilient. I'm excited to share in this post the design decisions and trade offs we've made!
Background
Major is a platform where users can build, share and deploy apps, agents and workflows (our primitives). Connectors can be used by any of the primitives and are configured and permissioned on an organizational level. We integrate with traditional SCIM / IdP providers to import user groups directly into Major.
Problem
Say Alice is building a system on Major that identifies high-value signups and awards them free credits once they hit certain onboarding milestones. She creates a workflow triggered on user signups. The workflow first checks the CRM to see whether the customer already exists. If not, it invokes an agent to determine whether the customer fits the company's ICP. If the conditions are met, the workflow pushes the identified user into an app where Alice (or anyone else on the BDR team) can send them an email and grant free credits.
There's an authorization question at every step. Which apps and agents is the workflow allowed to invoke? Which connectors can the agent access? What permissions does Alice need to build and deploy the workflow? What permissions does someone on the BDR team need to use the resulting app? And what happens to the deployed system if Alice's access changes or she leaves the company?
What the system needs to do
Primitives are only useful if they touch real systems - production databases, systems of record, internal APIs. Everything that follows is about granting that power without losing control of it. Concretely:
- Credentials and enforcement can never live in generated code. Primitives are largely written by AI and external builders bring their own harnesses. Both the code and the client running it are untrusted, so secrets must never be readable by a primitive and no security decision can depend on the client's behavior.
- Least privileged: A deployed primitive should hold the minimum permissions it actually needs.
- Sharing a primitive must not mean sharing downstream objects. Letting a teammate use a customer-health app can't require provisioning them on the production database behind it
- Collaboration must not enable impersonation. Many people build on one primitive, but edit access to someone else's app can never become a way to act with their identity.
- Auditable and live revokes. Every object access traces back to both the primitive and the human behind the request, especially for connectors. Revokes are live and happen instantly.
How the system works
The core idea is simple: users never give primitives credentials. They delegate a bounded subset of their authority to a deployment identity, and every use of that authority is mediated by Major's proxy.
Permissions and Objects
We have 4 objects in Major: apps, agents, workflows and connectors.
The primitives all have the exact same permission structure. At a high level you can either build or use. Build gives you the ability to make edits. Use dictates whether you can use the primitive.
Connectors have a build permission, which determines whether a user can build or deploy a primitive that uses that connector.
Object Use Proxy
The proxy is how we secure access against both your connectors and primitives throughout the system.
Connector credentials are never exposed to any primitive. They are stored encrypted at creation time and used only through our proxy. Everything that touches the connectors communicates through the proxy. This includes:
- Primitives (apps, agents and workflows)
- Major Agent (our agent harness that is optimized to build around our primitives)
- MCP / External builders (users that build within their own harness i.e. Claude Code, codex etc.)
This proxy allows us to enforce AuthZ but also enables audit logging, rate limiting and advanced features like connection pooling and caching. Primitives that invoke other primitives also flow and are permissioned through this proxy.
Scoped Deployment Identities
When a primitive is deployed, we create a scoped identity that contains:
- Delegating principal - this is the user who deployed the primitive
- Scope - this is the minimal subset of permissions that the primitive requires
We programmatically derive the scope from the way our primitives are structured. Notably here, the scope is against any of our objects. Agents can use apps or workflows, workflows can use agents or apps, etc.
If a user creates a primitive with access to objects that are not within their permission set:
- The deploy will fail
- Run time will also fail (explained below)
When a primitive is redeployed by a collaborating user, the deployment identity always changes to the new deploying user. This way, 1 user cannot piggyback off another user's identity through build access to their primitive.
Enforcement
When a user uses a primitive that uses one of our objects, the proxy receives:
- A token identifying the primitive
- An ephemeral user JWT identifying the user making the request
When checking permissions, we check both:
- The deployer's (delegating principal's) current permission set
- The immutable scopes on the deployment identity
This way, when access is revoked on the principal, it is instantly revoked on all primitives that they have built. Notably, access can be regranted if a different user with the requisite auth redeploys the primitive.
For per user connectors (e.g. Gmail where each user has their own credentials), we use the invoking user JWT to determine which credentials to fetch. Audit logging is also enforced against this second user's token.
Strict Mode
By default we do not check AuthZ on the user JWT against the underlying object. This is the way most orgs operate. E.g. If someone creates an app that uses their production DB to monitor customer health, when they deploy that app, they do not want to provision production DB access to every user downstream who needs to use that app. Instead it's inherited against the deployers permission set.
However, for the orgs that do want this second layer of provisioning, we offer strict mode. This requires users to grant use permissions on an object in order for them to use that object through a deployed primitive and is an extra check on our enforcement layer.
Tool permissions
We believe that harness level tool permissions are both insecure and create unnecessary decentralization. To this end, we've created a second level of tool permissioning on our proxy layer specifically for agent identities. It works by requiring non-allowlisted tool requests to obtain an ephemeral token from our proxy before execution.
Our harness uses a hook to process a request-tool-permission response from our proxy. This allows us to enforce the tool allowlist at the server level rather than the client.
Tradeoffs
Delegated authority creates a confused-deputy risk
By default, users of a deployed primitive do not need direct access to the objects behind it. The primitive is intentionally allowed to exercise a subset of its deployer's authority on their behalf. The scoped identity token is never exposed to the user so it is the primitive builder's job to ensure that their primitive does not expose endpoints or tool permissions that they are uncomfortable with.
For most organizations, this is fine since internal users tend to not be nefarious actors (especially when everything they do is audit logged). For organizations that don't have this level of trust, they can turn on strict mode.
Every deployment's authority is anchored to a human principal
The tradeoff here is that primitives can break when an org change happens or someone leaves the company. The way we mitigate this is by exposing in the UI when a primitive loses its permissions so that another user (or an org admin) can go in and redeploy the app granting it a new principal.
One giant proxy chokepoint
This adds a network hop to each request and is also a lot to maintain. A lot of elbow grease has been put in to make connector client SDKs compatible through our proxy. At the end of the day, this enables a lot of what makes Major great: audit logging, permissions, rate-limiting, caching etc. The tradeoff is we need to build things like connection pooling and bespoke auth patterns.