An agent built on the OpenAI Agents SDK and the Responses API holds an API key, calls tools with real credentials and sends conversation data to hosted services, so a single over-scoped key or an unapproved tool call can turn a model mistake into a security incident. The SDK gives developers guardrails, approval gates, tool filters and tracing controls, but most of them are opt-in or default to the permissive setting. This guide covers the identity and access model of the OpenAI API platform (organisations, projects, roles, service accounts and key permissions), how tools, MCP servers and handoffs extend an agent's reach, what to log and alert on, and a hardening baseline with the exact SDK settings. It is written for platform engineers, application security teams and identity teams who review or run agents in production. For framework-neutral controls see the Securing Agent Frameworks Guide.
Key takeaways
- The API key is the agent's identity. The SDK reads
OPENAI_API_KEYby default and, unless told otherwise, uses the same key to export traces. Give each agent workload its own project service account key with Restricted permissions. - Guardrails are not blocking by default. Input guardrails run in parallel with the agent (
run_in_parallel=True), so tools may already have run when a tripwire fires. Setrun_in_parallel=Falsewhere side effects matter. - Approval is opt-in for the riskiest tools. Local
ShellToolandApplyPatchTooldefault toneeds_approval=False, and the SDK's approval step is not a sandbox. Isolation is your job. - Hosted tools sit outside the tool guardrail pipeline. Hosted MCP, web search, file search, code interpreter and computer tools, plus handoffs, are not covered by
tool_input_guardrails; control them withrequire_approval,allowed_toolsand handoff checks. - Traces contain sensitive data unless you say no.
trace_include_sensitive_datadefaults toTrue, and Responses objects are stored for 30 days by default. Decide on tracing,storeand retention before go-live.
How identity and access work in OpenAI Agents SDK and Responses API
The Agents SDK is a client library: it runs in your process, builds Responses API requests and executes local tools. Every model call, hosted tool call and trace export authenticates to OpenAI with an API key that belongs to a project inside an organisation. The SDK itself has no user model, so who the agent acts for, and what it may do, is decided by the key you give it and the tools you attach.
| Layer | Identity or credential | Where it is set | Security note |
|---|---|---|---|
| Organisation | Human members with organisation roles | API platform settings | Organisation owners can see all projects, users and keys |
| Project | Project members and service accounts | Project settings | Resources such as files and stored state are scoped to the project and cannot be moved |
| API key | User-owned key or service account key | OPENAI_API_KEY, set_default_openai_key() or a custom client | Permissions All, Restricted or Read Only; the secret is shown once |
| Tracing | Same key as model calls by default | set_tracing_export_api_key() | Use a separate key with use_for_tracing=False when model traffic goes elsewhere |
| Hosted MCP and connectors | OAuth access token in authorization | HostedMCPTool tool_config | Not stored by the Responses API; sent on every request |
| Local MCP and function tools | Whatever credential your code holds | MCPServerStreamableHttp headers, tool code | The model chooses when the credential is used |
| End user | Hashed safety_identifier | Responses request parameter | Abuse attribution only, not authorisation |
Two consequences follow. First, the agent has no notion of the end user's permissions unless your tools enforce them, which is the core of OWASP LLM06:2025 Excessive Agency (excessive functionality, permissions and autonomy). Second, a leaked key gives an attacker the same model, tool and stored-data access as the agent. See the AI Agent Authorisation Guide.
Where state lives
- Client-managed:
result.to_input_list()and SDKsessionstores keep history in your storage, under your controls. - OpenAI-managed:
conversation_id(Conversations API) andprevious_response_idkeep state server side. Anyone holding a key for that project can continue the chain. - Retention: OpenAI documents a 30-day application state retention for the Responses API by default or when
store=true, plus 30-day abuse monitoring logs. With Zero Data Retention enabled for the organisation,storeis always treated asfalse. Hosted containers, audio outputs and remote MCP servers have their own retention behaviour.
Admin roles and privileged access
Platform roles decide who can mint the keys agents use. Treat them as privileged access and keep the owner population small.
| Role | Can do | Risk if compromised | Control |
|---|---|---|---|
| Organisation owner | Create projects, view all projects, users and API keys, set organisation and project spend limits, archive projects | Full control of every agent's credentials and spend | Two to three named owners, SSO with phishing-resistant MFA, quarterly review |
| Organisation reader | Join projects, run inference, create keys in those projects | Can create new keys in assigned projects | Assign to projects only where needed |
| Project owner | Member rights plus add users, manage settings, set project spend limits, manage service accounts, archive the project | Can create service accounts and change key permissions | One owner per project from the owning team, not every developer |
| Project member | Run inference, use resources, create project keys, read and modify data | Keys with default All permissions | Restrict key creation through custom roles where available |
| Admin API key holder | Call organisation administration endpoints such as /v1/organization/audit_logs | Organisation-wide administrative reach | Store in a vault, use only from the security tooling account |
- Default project: every organisation has a Default project that cannot be deleted and inherits the organisation's full configuration. Do not run production agents in it; create one project per agent or per environment.
- Archiving is permanent: an archived project cannot be restored, so archive only after keys and service accounts have been retired and logs exported.
- Spend limits: project limits only monitor by default. Turn on "Enforce a hard limit" for agent projects; OpenAI notes enforcement is not instantaneous, so spend can slightly exceed the limit. This also contains OWASP LLM10:2025 Unbounded Consumption.
See the Agentic AI Security Guide for how these roles fit a wider agent governance model.
Integrations, API tokens and service accounts
Service accounts and key permissions
- Use service accounts for agents: a service account represents a system, is scoped to one project and cannot be used outside it. Creating one issues a key immediately; the secret is shown once, so write it straight into a vault. See the Credential Vaulting for AI Agents Guide.
- Change the default: a new service account key can read and write all API resources in its project, and its permissions cannot be set in the creation dialog. Edit it afterwards to Restricted, granting Write only to endpoints the agent calls (typically Responses) and None for files, vector stores or fine-tuning it never touches.
- No user-owned keys in production: user-owned keys default to All permissions and leave with the person. They also break attribution in audit logs.
- Never in the client: keep keys out of browser and mobile code, notebooks and repositories. The Microsoft Azure OpenAI abuse by Storm-2139 showed exposed customer AI service keys being collected and resold as a hacking-as-a-service offering. See the LLMjacking Guide.
Tool credentials
| Tool type | Where it runs | Credential path | Primary control |
|---|---|---|---|
Function tools (@function_tool) | Your process | Your code's credentials | tool_input_guardrails, tool_output_guardrails, needs_approval, least-privilege backend scopes |
Local MCP (MCPServerStdio, MCPServerStreamableHttp) | Your infrastructure | Authorization header in params["headers"] | tool_filter, require_approval, server-level tool guardrails |
Hosted MCP (HostedMCPTool) | OpenAI calls the server | OAuth token in authorization | allowed_tools, require_approval, on_approval_request |
| Hosted tools (web search, file search, code interpreter) | OpenAI infrastructure | Project key | Attach only when needed; restrict project key permissions |
ShellTool, ApplyPatchTool (local) | Your host | Host and process privileges | needs_approval=True, container or VM isolation, no secrets in environment |
ComputerTool | Your harness | Signed-in sessions in the browser or desktop | on_safety_check, dedicated low-privilege profile |
Handoffs and Agent.as_tool() | SDK pipeline | Inherits run context | input_filter, checks in on_handoff, needs_approval on as_tool |
- Tokens in headers, not URLs: the SDK's MCP documentation says to connect only to trusted servers, use least-privilege credentials, keep access tokens in authorisation fields or headers rather than URLs, and require approval for sensitive operations.
- Remote MCP servers see your context: OpenAI warns that a malicious server can exfiltrate sensitive data from anything that enters the model's context, and data sent to a remote server follows that server's retention policy. For private servers, Secure MCP Tunnel (
tunnel_id) avoids exposing them publicly. See the MCP Security Guide. - Per-user OAuth where the tool acts for a person: pass the user's delegated token to the tool rather than a shared integration token, so the backend enforces the user's own rights. See the OAuth Token Exchange and Delegation Guide.
- Keyless where the platform allows: for your own backends, prefer workload identity federation over static secrets in the agent's environment. See the Cloud Workload Identity Guide.
For a wider view of tool risk, see the AI Agent Tool and Plugin Security Guide; for agents calling other agents, see the Multi-Agent and A2A Security Guide.
Logging and detection
Three log sources matter: the organisation audit log (who changed keys, roles and projects), SDK traces and application logs (what the agent did), and your tool backends (what the credentials actually touched).
| Source | Event or field | What to alert on |
|---|---|---|
Audit logs API (GET /v1/organization/audit_logs, admin key) | api_key.created, api_key.updated, api_key.deleted | Keys created outside the provisioning pipeline; permission changes to All |
| Audit logs API | role.assignment.created, user.added, project.created, project.archived | New owners, projects outside naming policy |
| Audit logs API | ip_allowlist.updated, login.failed | Network control changes; repeated failed logins to the platform |
| Usage and spend | Project spend alerts | Spikes per key or project, a classic sign of a stolen key |
| SDK exceptions | InputGuardrailTripwireTriggered, OutputGuardrailTripwireTriggered, ToolInputGuardrailTripwireTriggered, MaxTurnsExceeded | Tripwire rate per agent; repeated tripwires from one safety_identifier |
| Approval flow | ToolApprovalItem in RunResult.interruptions, approve or reject decisions | High approve rates (rubber-stamping), sticky always_approve decisions |
| Hosted MCP output items | mcp_list_tools, mcp_call, mcp_approval_request | Tool lists changing between runs; calls to tools not in allowed_tools policy |
- Tracing choices: tracing is on by default. Disable globally with
OPENAI_AGENTS_DISABLE_TRACING=1orset_tracing_disabled(True), per run withRunConfig.tracing_disabled, or keep it and setRunConfig.trace_include_sensitive_data=False(environment variableOPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA) so generation and function spans omit inputs and outputs. Disabling tracing does not discard data already buffered. - Application logs: the SDK uses the
openai.agentsandopenai.agents.tracingloggers. KeepOPENAI_AGENTS_DONT_LOG_MODEL_DATA=1andOPENAI_AGENTS_DONT_LOG_TOOL_DATA=1in production so verbose logging does not write prompts and tool payloads to disk. - Attribution: send a hashed
safety_identifierper end user so abuse can be traced to an account, and correlate it with your own request IDs.
Metrics that show it is working
- Percentage of production agent keys that are service account keys with Restricted permissions (target 100%).
- Number of organisation and project owners, reviewed quarterly against a named list.
- Percentage of write-capable or destructive tools with
needs_approvalorrequire_approval="always". - Median age of agent API keys against your rotation policy, and time to revoke after a leak alert.
- Guardrail tripwire rate per agent and per thousand runs, with trend.
- Approval rejection rate: near zero over months usually means reviewers are not reading.
See the AI Agent Observability and Incident Response Guide.
Hardening baseline
| Control | Setting | Default | Why |
|---|---|---|---|
| Blocking input guardrails | @input_guardrail(run_in_parallel=False) for agents with side-effecting tools | Parallel | Prevents tools running before the tripwire fires |
| Tool guardrails | tool_input_guardrails to block secrets or out-of-policy arguments; tool_output_guardrails to redact | None | Checks each call, not just the final answer |
| Pre-approval checks | ToolExecutionConfig(pre_approval_tool_input_guardrails=True) | Off | Reviewers only see calls that already passed policy |
| Approvals | needs_approval=True or a callable on write tools; require_approval="always" on hosted MCP | Off for local shell and patch | Human or programmatic gate for destructive actions |
| Tool surface | tool_filter (local MCP), allowed_tools (hosted MCP) | All server tools | Removes excessive functionality |
| Handoff history | input_filter or RunConfig.handoff_input_filter | Full history passed | Stops sensitive tool output reaching a less trusted agent |
| Turn limit | Keep max_turns set; never max_turns=None in production | Set | Bounds loops and spend |
| Trace data | trace_include_sensitive_data=False | True | Keeps prompts and tool data out of traces |
| Stored state | store=false or ZDR where the use case allows | Stored 30 days | Reduces data held server side |
| Spend | Project hard limit plus alerts | Monitor only | Contains key abuse and runaway agents |
- Keep untrusted text out of instructions: OpenAI's agent safety guidance says to pass untrusted inputs through user messages, not developer messages, and to use structured outputs (enums, fixed schemas) between agents so there is no free-form channel for smuggled instructions. This addresses LLM01:2025 Prompt Injection and ATLAS AML.T0051.001 (indirect prompt injection). See the Prompt Injection Guide.
- Authorise inside handoffs:
is_enabledis evaluated before the model supplies arguments, so it cannot authorise argument values. Do the check at the start ofon_handoffand raise on failure. - Do not call
__wrapped__in production paths: it bypasses schema validation, guardrails, timeouts and tracing. - Scope sticky approvals:
always_approve=Truepersists in theRunContextWrapperand in serialisedRunState, and can carry across handoffs to a tool of the same type and name. Use per-call approvals where permissions differ, and a fresh wrapper for new runs.
Rollout plan
- Inventory: list every agent, its project, key type, key permissions, tools and MCP servers. Export the audit log baseline.
- Re-key: create a project and service account per agent, set Restricted permissions, store keys in a vault, and move traffic over. Keep the old key for a short overlap, then revoke.
- Observe: deploy tool guardrails that log but do not trip (return
tripwire_triggered=Falsewithoutput_info) for two weeks to measure false positives. - Enforce in a pilot: turn on blocking guardrails, approvals and tool filters for one agent, then expand by risk tier. Roll back by reverting the agent configuration; keys and projects stay as they are.
- Operate: wire audit log events and tripwire metrics into the SIEM, run quarterly role reviews and red-team exercises with injected tool content.
Small team or large estate: a small team can start with one project per environment, Restricted keys and approvals on every write tool. A larger organisation should run one project per agent, enforce key creation only through a pipeline, alert on any api_key.created event from a human, and put an MCP gateway in front of shared servers. See the MCP Gateway and MCP Server Governance Guide.
Framework mapping
| Risk | ATT&CK / ATLAS | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | OWASP LLM 2025 |
|---|---|---|---|---|
| Leaked or stolen API key | T1552.001 Credentials In Files, T1078.004 Cloud Accounts | IA-5, IA-9 | PR.AA-01 | LLM10 |
| Over-scoped tools and keys | AML.T0053 AI Agent Tool Invocation | AC-6, CM-7 | PR.AA-05 | LLM06 |
| Injected instructions in tool content | AML.T0051.001 Indirect | SI-10 | DE.CM-09 | LLM01 |
| Data exfiltration through tools | AML.T0086 Exfiltration via AI Agent Tool Invocation | AC-4 | PR.DS-02 | LLM02 |
| Missing audit trail | Not applicable | AU-2, AU-12 | DE.CM-03 | Not applicable |
Common misconfigurations
- One key for everything: a single user-owned key with All permissions in development, CI and production. A leak from any one place exposes all of them, and the audit log cannot tell which agent did what.
- Guardrail that fires too late: an input guardrail left in parallel mode on an agent that sends email or writes records. The guardrail trips, but the tool call has already happened.
- Shell tool on the host: a local
ShellToolwithneeds_approval=Falserunning in a container that also holds cloud credentials in environment variables. The SDK documentation is explicit that its approval is not a sandbox. - Hosted MCP with approvals off:
require_approval="never"copied from a sample into production, with every tool on the server allowed and a broad OAuth token inauthorization. - Full history on handoff: a triage agent with access to customer records hands off to a web-browsing agent, which receives the whole transcript, including tool outputs, because no
input_filterwas set. - Sensitive traces by default: tracing left on with sensitive data included, so prompts, customer data and tool results sit in a trace store that more people can read than the production database.
Practitioner checklist
- Run each production agent under its own project service account key with Restricted permissions, stored in a vault.
- Limit organisation owners to a short named list and review all owner and project owner assignments quarterly.
- Enable project hard spend limits and alerts for every agent project.
- Set
run_in_parallel=Falseon input guardrails for agents with side-effecting tools, and add tool input and output guardrails. - Require approval on every write or destructive tool, and scope
always_approvedecisions narrowly. - Restrict MCP tools with
tool_filterorallowed_tools, pass tokens in headers, and keep hosted MCP approvals on. - Isolate local shell, patch and computer tools in a sandbox with no secrets and minimal network access.
- Set
input_filteron handoffs and authorise argument-bearing handoffs inon_handoff. - Decide tracing,
trace_include_sensitive_data,storeand ZDR settings and document them. - Pull
/v1/organization/audit_logsinto the SIEM and alert on key and role changes.
Standards and references
- OpenAI: Agents SDK for Python (guardrails, tools, MCP, tracing, handoffs and human-in-the-loop documentation)
- OpenAI: Connectors and MCP servers
- OpenAI: Safety in building agents
- OpenAI: Managing projects in the API platform
- OpenAI: Data controls in the OpenAI platform
- OpenAI: Audit logs API reference
- OWASP: Top 10 for LLM Applications 2025
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: Securing Agent Frameworks Guide · Credential Vaulting for AI Agents Guide · AI Agent Tool and Plugin Security Guide · Multi-Agent and A2A Security Guide · Cloud Workload Identity Guide