Tools & permissions

What it can do, and as whom

These are the questions your security team is going to ask. I’d rather you hear the answers from me first.

The short version

Seven questions, straight answers

Every control below fails closed. If it isn’t configured, it isn’t allowed. Tap a question for the long answer.

Can someone act as another user?

No

Identity is always the verified caller or a trusted first-party assertion. It’s never something the client made up.

SurfaceIdentityVerified by
ChatThe Cognito sub in the bearer token on the socketAgentCore’s inbound JWT authorizer, against the shared user pool
POST /agent/sessions, /agent/connect, /agent/tasksThe Cognito sub of the API callerThe CoreApi Cognito authorizer
Run Agent Task eventThe principal in the event detailThe account-internal bus: only first-party Lambdas can put events on it
Persistent agent task{ type: system, id: <type>/<agentId> }handleTask in your stack: the trigger’s agent must belong to the type and match the principal
Session ownershipThe userId stored at creationThe host compares it to the verified caller at connect and at task time and refuses a mismatch
Trust boundaries and the controls that guard each crossing PUBLIC INTERNETREADY, SET, CLOUD ACCOUNTEXTERNALBrowserchat userAPI callerapp · workflowAgentCore RuntimechatCoreApisessions · connect · tasksEvent busaccount-internalTask LambdarunAgentTaskBedrockmodel grantFirst-party backendsMCP serverallowlisted hostAny URLhttp_request1234567
Every crossing has a control, and every control fails closed. Inside the account, first-party emitters are trusted to say who a task runs as. From the outside, identity is always a verified token. The highlight steps through each control.
  1. 1Cognito JWT on every CoreApi call: the caller’s sub becomes the identity.
  2. 2AgentCore’s inbound JWT authorizer on the socket, then the runtime’s session-owner check.
  3. 3McpAllowedHosts when a session is created: an MCP URL outside the allowlist is rejected.
  4. 4SystemTaskPrincipals before an API caller may run as a system id.
  5. 5Only first-party Lambdas can put events on the account-internal bus.
  6. 6Outbound MCP calls carry the authority-minted authHeader, applied after user headers.
  7. 7http_request lets the model pick the URL. Turn it on deliberately and put a fence around egress.

Can one person’s chat or memory leak to another?

No

Sessions are owned, and memory is scoped by the verified id.

Every session stores its owner when it’s created, and the host compares that owner to the verified caller on every connect and every task. So leak or guess a sessionId and you still can’t resume someone else’s conversation.

Long-term memory is namespaced by the verified sub (/facts/{sub}, /users/{sub}/preferences), not by anything the client sends. Recall never crosses users.

Can an API caller run as a system?

Only with a grant

Only with an explicit grant in a deploy parameter, the same way the MCP allowlist works.

Autonomous work runs as one of two identities, and they don’t get the same trust. { type: "user", id: sub } is a verified person, and it’s the default for POST /agent/tasks. { type: "system", id: "booked" } is a service with nobody behind it. A first-party emitter on the internal bus can claim a system principal for free, because only first-party code can reach that bus. Over the public API, claiming one is a privilege.

How a task’s principal is decided Task requestArrived how?Principal as assertedfirst-party emitter on the bussystem requested?body.systemuser principal{ type: user, id: caller sub }Grant exists?SystemTaskPrincipalssystem principalcreatedBy = caller sub403 · fails closedempty allowlist rejects alleventAPInoyesyesno
The bus gets to assert a principal because only first-party code can reach it. The public API derives one from the verified token and only hands out a system id when an allowlist entry says so.
WhereGate
Internal bus (requestAgentTask)None: a first-party emitter is already trusted to assert a principal.
POST /agent/tasks { system }The SystemTaskPrincipals deploy parameter: comma-separated sub:systemId grants; sub:* allows any id; empty rejects all. Fails closed.
Launcher recordWhen a person launches a system task over the API, their sub is stored as createdBy so they can still read the task back even though the run acts as the system.
# sub abc-123 may run as "booked" only; svc-sub may run as any system id
SystemTaskPrincipals="abc-123:booked,svc-sub:*"

Can the model run shell commands or touch the disk?

No

The SDK’s bash and file_editor are deliberately left out.

In a shared hosted runtime, shell exec is remote code execution, and filesystem access reaches the host’s disk. No thanks. A host that really wants them can import them from the SDK and register them itself, as an explicit choice that shows up in code review.

What a session can use is a registry of named first-party tools. Turning on a tool that already ships is a data change. Writing a new one is the only time you touch code.

import { tool, builtinTools, resolveTools, createAssistant } from '@readysetcloud/agent';
import { z } from 'zod';

// Host registry: built-ins plus the host’s own.
const TOOL_REGISTRY = {
  ...builtinTools,                                   // http_request, notebook
  get_current_time: () => tool({
    name: 'get_current_time',
    description: 'Returns the current server time as an ISO-8601 string.',
    inputSchema: z.object({}),
    callback: async () => new Date().toISOString(),
  }),
};

// A session opts in by name: { tools: ['http_request', 'get_current_time'] }
const tools = resolveTools(config.tools, TOOL_REGISTRY, { sessionId, userId });
const agent = createAssistant({ sessionId, tools: [...tools, ...mcpClients] });
Built-inWhat it doesWatch out
http_requestGeneric HTTP client. The web-search seam: point it at a search API and let the model read the JSON back. Also any public REST endpoint or webhook.An outbound-request surface: the model chooses the URL. Front it with an allowlist or prefer a scoped MCP gateway when egress matters.
notebookAn in-invocation scratchpad the model writes to and re-reads across steps of a run.State lives in the invocation, not on the host.

Can a session send the agent to any URL?

Only allowlisted

Only hosts in McpAllowedHosts. An empty list rejects everything, so it’s opt-in.

Attaching an MCP server is the one place a session gets to point the runtime at an outbound URL, so it gets the most scrutiny. Servers are keyed by label, and the host connects to each one at build time and adds its tools alongside the first-party ones. Secrets never live in the session row, since users create those. They come from the host’s environment through ${VAR} interpolation.

await createSession({
  userId,
  mcpServers: {
    blog: {
      url: 'https://mcp.example.com/mcp',
      headers: { 'x-api-version': '2025-01' },        // user-supplied; ${VAR} interpolated by the host
      authHeader: {                                     // authority-minted, forwarded verbatim
        name: 'x-booked-auth',
        value: '<base64url(payload)>.<sig>',
      },
    },
  },
});
ControlOwnerRule
SSRF allowlistHost (create-session)Rejects any mcpServers whose URL host is not in McpAllowedHosts. Empty rejects all, so it is opt-in.
SecretsHost environment${VAR} in string fields resolves against the host’s env. A secret never belongs in the session row, which the user creates.
authHeaderAn authority (a trusted server-side session creator)Mints a token binding tenant, user, session, and a version; the runtime forwards it verbatim, applied after user headers so they cannot shadow it, with no interpolation. The MCP server verifies it with the shared secret.

The other outbound door is the http_request built-in, which lets the model pick the URL. Turn it on deliberately, and put a fence around egress when that matters.

Can an MCP server trust who’s calling?

Yes

Yes, when a trusted authority mints an authHeader for the session.

The runtime is just a courier. A trusted server-side session creator mints a token that binds tenant, user, session, and a version. The runtime forwards it verbatim, after the user’s headers so they can’t shadow it, and never looks inside. The MCP server checks the signature with a shared secret.

authHeader: carrying verified identity to a per-tenant MCP server Authoritymints HMAC overtenant.user.session.vSession rowmcpServers.blog.authHeaderRuntimeforwards verbatimafter user headersMCP serververifies HMACtrusts tenant + userScoped resultsowner’s data onlyshared secret · never in the row, never in the runtimestoreloadheadertool results
The runtime is just a courier. It forwards the token verbatim as the configured header (here x-booked-auth) and never looks inside. The token binds tenant, user, session, and a version, so a leaked one can’t hop to another session, and the authority revokes it by bumping the version. Hit replay to follow the token.

The threat model, plainly: the HMAC proves the authority minted this token for user U. It does not prove the presenter is this runtime. That’s fine here because users can’t reach the config row, the host is allowlisted, and transport is TLS. The token gets replayed on every reconnect for the session’s ~30-day life, so bind it to the sessionId and include a version the authority can bump to revoke it.

What can a run reach?

Depends where it runs

The shared runtime and task Lambda enforce their boundaries for you. In library mode, your stack owns them.

BoundaryChat runtimeTask LambdaLibrary mode · persistent agents
BedrockThe deploy’s model grantSame grantYour stack’s
TableAgentChatTable, by sessionSame table, by task and sessionAny table via tableName
Cross-session memoryAgentCore Memory, per verified userNone by defaultWhatever memoryManager you pass
Outbound networkMCP hosts in the allowlist; http_request if enabledSameYours
IdentityVerified JWTEvent principal or verified JWTYours

Library mode means passing tableName and running the core in your own stack, which is exactly how every persistent agent runs. It doesn’t get the shared runtime’s guarantees for free. Your stack owns the Bedrock grant, the allowlist, and memory isolation.