Function Identity and Least Privilege

Describe execution roles, service identities, resource-scoped permissions, and why overbroad permissions are one of the most dangerous serverless anti-patterns.

Function identity is the security principal a serverless function uses when it calls other services. In practice, that identity often has more reach than any human operator because it runs automatically, at scale, and in response to many triggers. That is why least privilege is one of the most important serverless design rules. If the execution identity is overbroad, every triggered invocation inherits that overreach.

The right question is not just “can this function access the resource?” It is “what is the smallest permission set this function needs for this one job?” Serverless systems often break least privilege by attaching one shared role to many functions or by granting wildcard access because it is faster during initial setup.

    flowchart LR
	    A["Event trigger"] --> B["Function"]
	    B --> C["Execution identity"]
	    C --> D["Object storage: one prefix"]
	    C --> E["Queue: one topic"]
	    C --> F["Secrets store: one secret"]

What to notice:

  • the function does not act as an anonymous unit of compute
  • permissions should be scoped to the resources and actions required by that function
  • one shared overpowered identity turns many handlers into one big trust boundary

One Function, One Purpose, One Permission Shape

Least privilege works best when the function itself has a narrow purpose. If one handler uploads files, processes invoices, reads customer records, and sends notifications, the identity naturally grows broad. Good security and good function boundary design often reinforce each other.

That leads to practical rules such as:

  • separate functions by responsibility
  • grant resource-scoped permissions instead of platform-wide rights
  • split read and write capabilities when possible
  • avoid one shared execution identity for unrelated flows
 1function_identity:
 2  function: generate-thumbnail
 3  permissions:
 4    object_storage:
 5      read_prefix: uploads/images/
 6      write_prefix: derived/thumbnails/
 7    queue:
 8      publish: image-processing-results
 9    secrets:
10      read:
11        - image-signing-key
 1export async function handleImageCreated(event: { objectKey: string }) {
 2  if (!event.objectKey.startsWith("uploads/images/")) {
 3    throw new Error("Object outside approved processing boundary");
 4  }
 5
 6  const file = await objectStore.read(event.objectKey);
 7  const thumbnail = await imageService.createThumbnail(file);
 8  await objectStore.write(
 9    event.objectKey.replace("uploads/images/", "derived/thumbnails/"),
10    thumbnail
11  );
12}

What this demonstrates:

  • the handler assumes a narrow storage boundary
  • the identity only needs access to specific prefixes and outputs
  • business scope and permission scope are aligned

Resource Scope Beats Platform Scope

The most dangerous anti-pattern is a role that can:

  • read all buckets or containers
  • publish to all queues or topics
  • read all secrets
  • call broad administrative APIs

That usually happens because broad permissions make integration easier in the short term. The cost is that one compromised or buggy function can now reach unrelated systems. In serverless, that blast radius grows quickly because invocations scale automatically.

Common Mistakes

  • reusing one execution identity across many unrelated functions
  • granting wildcard resource access because exact scoping feels inconvenient
  • combining broad data read access with broad event publishing access
  • treating least privilege as cleanup work instead of core design work

Design Review Question

A team uses one shared service identity for all background jobs because it simplifies deployment. One image-processing function is later exploited through malformed input and gains access to billing exports and production secrets. What should the design review challenge first?

The stronger answer is the shared-identity model itself. The vulnerability mattered, but the architectural mistake was collapsing many unrelated trust boundaries into one overpowered execution role. Splitting function responsibilities and permission scopes would have limited the blast radius significantly.

Check Your Understanding

Loading quiz…
Revised on Thursday, April 23, 2026