January 01, 1970
Serverless computing has seen rapid adoption in cloud deployments, yet the security implications of its service-oriented programming model remain poorly understood. Distributed, modular, and heterogeneous applications complicate the specification of precise security policies. Role-based access control solutions such as Identity and Access Management (IAM) already exhibit pervasive misconfiguration problems, and the multiplicity of functions, services, and resources in serverless applications, together with frequent permission model changes by cloud providers, greatly increases the likelihood of policy misconfigurations. Consequently, policies are often overprivileged, thereby enlarging the attack surface and exposing sensitive cloud resources to compromise.
We present a large-scale measurement study of overprivilege in real-world serverless applications, analyzing a curated dataset of 689 AWS Lambda applications comprised of 1,293 functions. To enable this study, we develop PrivLess, a static policy analysis framework that extracts function-to-resource interactions from application source code, derives an interaction-permission mapping, and reconciles inferred interactions with declared policies to quantify overprivilege. Our measurement reveals that overprivilege is systemic and severe across the serverless ecosystem: 47.7% of applications carry excess permissions with a significant privilege reduction potential of 99.65%. Applications with wildcard-defined permissions exhibited an average overprivilege ratio 274\(\times\) higher than those without. More critically, the excess permissions enable concrete attack vectors: 18.8% of applications hold unnecessary Privilege Escalation capabilities, and 12 applications had Defense Evasion permissions they did not need.
Access Control, Security Policy Analysis, Serverless cloud security
We consider an adversary whose objective is to exploit overprivileged serverless function’s IAM execution role to access sensitive cloud resources reachable from the function, and extend their reach beyond the compromised function. With excess
permissions, an attacker can perform many unauthorized actions including the following: (i)exfiltrate data from S3 buckets, DynamoDB tables, or Secrets Manager entries that the function was never intended to access; (ii)move laterally to
services unrelated to the function’s purpose; (iii)escalate privileges (e.g., in AWS, via actions such as iam:PassRole or sts:AssumeRole) to acquire broader permissions; and (iv)establish persistence by creating
new resources or modifying existing policies.
We assume that developer-defined policies are functionally correct and authorize the function to perform its intended operations. However, we make no assumption about the minimality of these policies; they may grant significantly more permissions than necessary for operational purposes. This allows PrivLess to use developer policies as an authoritative reference for legitimate function behavior while still identifying and quantifying over-privilege.
We do not consider malicious developers (insider threat), compromise of cloud infrastructure, or resource-based policies (e.g., Lambda resource policies) that grant access independently of the execution role.
To the best of our knowledge, there is only one publicly available dataset of serverless applications [1]. However, that dataset was curated in 2021, and the serverless ecosystem on GitHub has grown substantially since then. To address this gap, we curated a new dataset as described below. We focus on the Serverless Framework [2] because it is a widely adopted serverless deployment tool, and has standardized configuration that enables consistent large-scale static analysis without external policy lookups.
We curated our dataset using a multi-stage filtering of GitHub repositories. First, we queried GitHub’s Search API for repositories explicitly tagged with "serverless" in their metadata, which returned 167,290 repositories. To filter out experimental or incomplete projects, we applied a minimum threshold of 10 GitHub stars and excluded repositories created before 2014, when serverless computing emerged. This initial filtering reduced the dataset to 5,227 repositories, of which 69% had been updated within the past year, indicating active maintenance. The collection phase was conducted over three days (October 17–19, 2025).
Next, we examined the cloned repositories to identify those built with the Serverless Framework [2] by checking for the presence
of a serverless.y{a}ml configuration file, which is required to use the framework. This filtering yielded 1,273 repositories.
Finally, we accounted for monorepository practices (large repositories containing multiple independent applications) by extracting each serverless application as a separate entity and excluding non-serverless applications. This extraction process
identified 3,714 applications from the 1,273 repositories. Of these, 351 applications (9.45%) were excluded due to parsing failures: invalid configuration syntax (82), unimplemented intrinsic function calls (17) such as Fn::FindInMap,
Fn::Sub, and Fn::ImportValue that required live API access; conversion errors (89) from unhandled event types and type mismatches; and missing provider or runtime sections (163) required for analysis. Our final dataset contained
3,363 serverless applications.
| Cloud Provider | App Count (%) |
|---|---|
| AWS | 2553 (75.91%) |
| Scaleway | 41 (1.22%) |
| Azure | 39 (1.16%) |
| 29 (0.86%) | |
| Others (OpenWhisk, Cloudflare, etc) | 701 (20.85%) |
| Total | 3363 (100%) |
6pt
As shown in Table 1, the dataset is heavily skewed toward AWS. Amazon Web Services (AWS) dominates with 2,553 applications (75.91%), while Microsoft Azure Functions and Google Cloud Functions account for 39 (1.16%) and 29 (0.86%), respectively. The remaining 20.85% of applications use alternative providers, including OpenWhisk, Tencent, Cloudflare, Kubeless, and OpenFaaS.
We designed PrivLess, a policy overprivilege analysis pipeline that quantifies and reduces overprivilege in serverless applications. At its core, PrivLess analyzes application source code to extract all function-to-cloud-service interactions (i.e., service requests), generates minimal permission sets based on the observed interactions, and reconciles them with developer-defined IAM policies to identify excessive permissions. It then synthesizes refined policies containing only necessary privileges (Figure 1). To enable this analysis, we first construct an offline curated IAM Knowledge base that maps each API endpoint (SDK call) to its minimum required permissions.
Determining whether a serverless function holds excess permissions requires knowing the minimum permission(s) each service request actually needs. While cloud providers document which permissions are required for specific actions, this information is fragmented across multiple sources(e.g., SDK documentation, API references, managed policies) and is often incomplete or difficult to reconcile. To address this, we construct a curated IAM Knowledge Base by running six complementary extraction strategies over the provider’s SDK artifacts and public documentation, then converging their outputs into a single confidence-annotated registry.
| ID | Strategy | Signal | Source (Sample per provider) |
|---|---|---|---|
| #S1 | Direct mapping | Leverages near-universal naming conventions mapping of cloud SDK methods to their corresponding service permissions or actions | AWS: AWS SDK (e.g., botocore service-2.json) [3]; Google Cloud: API client libraries [4]; Azure: Azure SDK [5] |
| #S2 | Regex mining | Extracts conditional permissions embedded in prose documentation in code, keyed to the parameter that triggers them | AWS: botocore doc strings [3]; Google Cloud: Cloud API documentation [6]; Azure: API reference [7] |
| #S3 | Semantic annotations | Machine-readable permission annotations authored by the cloud provider’s service team, providing cross-service dependency signals | AWS: AWS SDK models (Smithy) [8]; Google Cloud: API definitions [9]; Azure: OpenAPI definitions [10] |
| #S4 | Heuristic rules | Structured rule table encoding cross-service patterns (encryption, identity delegation, networking, container registries) that fire on matching parameter names | Parameter-shape analysis from service SDK specifications (provider-agnostic) |
| #S5 | Authorization reference | Ground-truth enumeration of all valid actions/permissions; serves as both data source and validation corpus | AWS: IAM Authorization Reference [11]; Google Cloud: IAM Roles Reference [12]; Azure: RBAC Built-in Roles [13] |
| #S6 | Provider managed policies | Service-to-service dependency index derived from cloud-provider-managed and service-linked policies; flags implausible cross-service entries | AWS: Managed policies corpus [14]; Google Cloud: Predefined roles [15]; Azure: Built-in roles [16] |
5pt
We employ six complementary strategies (#S1–Direct mapping, #S2–Regex mining, #S3–Semantic annotations, #S4–Heuristic rules, #S5–Authorization reference, #S6–Managed policies) to construct the curated Knowledge Base. Table 2 summarizes each strategy’s signal, extraction method, and data sources. #S1 and #S5 form the backbone: #S1 derives a primary permission for every operation using the cloud provider’s naming convention, while #S5 provides
the authoritative enumeration of valid IAM actions. Any permission #S1 derives that is absent from #S5 is flagged not_in_docs. #S3 contributes the most reliable cross-service signals, as its annotations are embedded in the SDK’s formal service
model. #S2 and #S4 surface conditional and implicit permissions not found in formal documentation, prioritizing recall over precision. Finally, #S6 acts as a statistical validator: cross-service permissions whose caller-to-callee service pair never appears
in the provider’s managed or predefined policies are down-weighted as likely false positives.
The outputs of the six strategies are merged per \(\langle\mathit{service}, \mathit{operation}\rangle\) pair via weighted voting with weights \(w_{\#S5}\!=\!w_{\#S3}\!=\!3\), \(w_{\#S1}\!=\!2\), \(w_{\#S2}\!=\!w_{\#S4}\!=\!1\). Algorithm 2 details the core convergence process. For each permission candidate \(p\), we accumulate the weights of all strategies that identified it, selecting the highest-scored candidate \(p^*\) and assigning confidence based on the cumulative score: \(\sigma \geq 4 \Rightarrow \mathrm{\small high}\); \(\sigma \geq 2 \Rightarrow \mathrm{\small medium}\); otherwise \(\mathrm{\small low}\). Post-convergence, quality flags may be applied to annotate entries with known limitations: for example, services whose IAM naming is incompatible with the 1:1 permission-to-operation mapping, or permissions derived by strategies but absent from the authoritative reference source. These flags inform downstream users of the knowledge base about entry confidence and applicability. Conditional and cross-service permissions are converged using the same weighted scoring and confidence assignment process.
At analysis time the registry is queried with a \(\langle\mathit{service},\mathit{operation},\mathit{params}\rangle\) triple. Conditional permissions are included only when their trigger parameter is present in the call, narrowing the permission set to what the specific invocation actually requires. This results in a per-function minimal permission set \(\mathcal{R}_f\) (defined formally in Section [sec:perm-inference]) used to compute over-privilege as \(\mathcal{G}_f \setminus \mathcal{R}_f\), where \(\mathcal{G}_f\) is the permission set granted to function \(f\)’s execution role [17].
With the IAM knowledge base setup, we analyze application source code statically to identify all interactions between a defined serverless functions and cloud services. A function \(f_i\) may invoke one or more service
API endpoints (wrapped by the cloud provider’s SDKs). We refer to the set of statically determined API calls as service requests, \(R\). Each request \(r_i = (f_i, a_i, q_i) \in R\) consists
of a service action \(a_i\) (e.g., s3.getObject) and a set of parameters \(q_i\) (e.g., (Bucket=bucket, Key=key)).
First, given a serverless app, we parse the configuration file to identify the entry functions requiring privilege assignment. Then, for each function, we construct a call graph to identify dependent functions and modules, ensuring that privileges required by such functions or inter-procedural calls are properly attributed to the entry function. We then trace the instantiations and use of a cloud provider’s imported SDK in the code to extract all the service requests, \(R\) invoked by the functions. \(R\) is passed to the next phase, which infers the resource objects targeted by these requests.
The parameters, \(q_i\), in a given service request, \(r_i\), may contain resource identifiers that the request targets. Such resource IDs (e.g., S3 bucket names or DynamoDB table names) are essential for ensuring that access controls are properly scoped to the intended resources. While many resource values are dynamically provisioned, we use data flow analysis [18] to trace parameter variables to their concrete values where statically determinable—from literal assignments, environment variables, or the serverless configuration file. Since multiple parameters may exist but only a subset specifies resources, we curate a resource key mapping for all applicable services based on provider’s documentation and attempt resource ID resolution only for parameters present in this mapping. Once a resource ID is successfully resolved, we replace the variable name in the initial request with the resolved ID. Otherwise, we retain the first declared or instantiated value—typically an environment variable—that flows into the parameter. We argue that, although static analysis cannot capture all resource IDs due to their dynamic nature, developers typically know which resources they intend to secure, making explicit specification straightforward. Moreover, by preserving unresolved environment variables in the policy, they resolve correctly to the intended resources at runtime.
Given the updated requests, \(R\), PrivLess generates the final IAM policy through four sequential steps: (1) A Permission Inference Engine queries the knowledge base for each request to produce the per-function inferred permission set \(\mathcal{R}_f\); (2) the Resource Generalizer consolidates resource identifiers into prefix wildcards, producing generalized requests \(\mathit{GR}\); (3) a relaxation step refines \(\mathcal{R}_f\) against the developer’s declared policy, yielding the relaxed set \(\mathcal{R}^{*}_f\); and (4) the Policy Compiler assembles \(\mathit{GR}\) and \(\mathcal{R}^{*}_f\) into the final policy \(P_N\).
Given the set of requests, \(R\), the Permission Inference Engine resolves the minimum IAM permissions required for each request by querying the
knowledge base (Section 3.1). For each request \(r_i = (f_i, a_i, q_i)\), the knowledge base is queried with the triple \(\langle \mathit{service}, a_i, q_i
\rangle\): it returns the primary IAM action and any parameter-triggered conditional permissions. For example, s3.getObject with VersionId in \(q_i\) resolves to
s3:GetObjectVersion, and to s3:GetObject without it. Moreover, conditional cross-service permissions (e.g., kms:GenerateDataKey when an SSE-KMS key parameter is present) are included only when the triggering parameter
is present in \(q_i\). The union of permissions resolved across all \(r_i \in R\) with \(f_i = f\) constitutes the per-function inferred required set \(\mathcal{R}_f\).
Serverless applications often access multiple objects or resources from a shared location. For example, an application may request these files from an S3 bucket: bucket/images/image1.jpg, bucket/images/image2.jpg, bucket/images/image3.jpg, and so on. Resource generalization summarizes these requests into a prefix wildcard that represents the shared directory: bucket/images/*. This allows PrivLess to generate a single IAM policy statement that grants the applicable function the necessary permissions for all objects in the directory, rather than creating separate statements for each individual object—an approach that would result in a complex and unwieldy policy. Moreover, this ensures that the generated IAM policy can accommodate future requests to resources in the same path without modification.
The Resource Generalizer groups the requests, \(R\), by function, \(f_i\). We limit resource generalization to requests made by the same function to ensure that the generated IAM policy is specific to each function’s operations and does not inadvertently grant other functions access to resources they do not require. For each group of requests, the Resource Generalizer examines the resource IDs in \(q_i\) to identify and generate prefix wildcards for resources sharing a common path. The common prefix, \(\mathit{cp}_i\), becomes the generalized resource identifier for all requests in that subset. Resources with unique paths are preserved without generalization. Finally, PrivLess outputs a set of generalized requests, \(\mathit{GR}\).
Taking \(\mathcal{R}_f\) (the per-function inferred required set from permission inference) and \(\mathcal{G}_f\) (the developer-defined grant set for function \(f\), parsed from the framework configuration) as inputs, this step addresses two limitations of purely static inference. First, \(\mathcal{R}_f\) may contain permissions the developer never intended to grant. Second, implicit dependencies absent from explicit SDK calls—such as runtime-emitted log writes—may be missing from \(\mathcal{R}_f\), causing legitimate permissions to be incorrectly flagged as over-privileged.
PrivLess applies a two-pass relaxation step. Pass 1 (Bounding). Let \(\hat{\mathcal{G}}_f\) denote the wildcard-expanded developer grant set, where patterns such as svc:* or
svc:Put* are treated as covering all matching actions. Any permission in \(\mathcal{R}_f\) not covered by \(\hat{\mathcal{G}}_f\) is evicted, yielding \(\mathcal{R}_f \cap \hat{\mathcal{G}}_f\). This bounds the inferred set strictly by developer intent, given the assumption that developer-defined permissions are functional(1). Any
false-positive permissions generated by the knowledge base are thereby corrected.
Pass 2 (Augmentation). Developer permissions not yet in \(\mathcal{R}_f\) are candidates for augmentation to recover actions the static analysis may have missed. Unrestricted augmentation—admitting any developer permission for a service statistically related to one the function uses—restores over-privilege that per-function scoping is designed to eliminate. We instead apply three complementary filters, all of which must hold for a candidate permission \(p \in \mathcal{G}_f \setminus \mathcal{R}_f\) to be admitted.
Let \(\text{svc}(p)\) denote the service prefix of permission \(p\), \(\text{svcs}(S) = \{\text{svc}(p) \mid p \in S\}\), \(\mathcal{R}_{\text{app}} = \bigcup_{g \in F} \mathcal{R}_g\) the union of inferred sets across all functions \(F\) in the application, and \(\text{level}(p)\) the IAM access level of \(p\) (e.g., Read, Write, List) drawn from the knowledge base.
A (Direct service). \(\text{svc}(p) \in \text{svcs}(\mathcal{R}_f)\): augmentation is restricted to services PrivLess directly observed for this function. Cross-service dependency is not a sufficient criterion; the co-occurrence of two services in the dataset does not imply that every permission of the dependent service is required by this function.
B (App-level static evidence). \(p \notin \mathcal{R}_{\text{app}}\): if PrivLess generated \(p\) for any other function in the same application, that demonstrates \(p\) is statically observable in this codebase. Its absence from \(\mathcal{R}_f\) is therefore evidence that function \(f\) does not exercise it, not that the analysis missed it.
C (Access-level compatibility). \(\text{level}(p)\) is compatible with the access levels already present in \(\mathcal{R}_f\) for service \(\text{svc}(p)\). A function whose detected calls are read-level should not receive write-level developer permissions through augmentation. When \(\text{level}(p)\) is absent from the knowledge base the filter passes conservatively.
\[\begin{align} \mathcal{R}^{*}_{f} &= \bigl(\mathcal{R}_f \cap \hat{\mathcal{G}}_f\bigr) \cup \Bigl\{ p \in \mathcal{G}_f \setminus \mathcal{R}_f \;\Big|\; \\[2pt] &\qquad \underbrace{\text{svc}(p) \in \text{svcs}(\mathcal{R}_f)}_{\text{A}} \wedge \underbrace{p \notin \mathcal{R}_{\text{app}}}_{\text{B}} \wedge \underbrace{\text{level\_comp}(p,\mathcal{R}_f)}_{\text{C}} \Bigr\} \end{align}\]
Because all augmented permissions are drawn from \(\mathcal{G}_f\), the relaxed set \(\mathcal{R}^{*}_f\) is always a subset of \(\hat{\mathcal{G}}_f\): the policy is grounded in developer intent and never broader than what the developer granted. The relaxation is configurable and can be disabled when the knowledge base alone is sufficient (e.g., an authoritative permission mapping from a Cloud provider is provisioned).
Finally, the Policy Compiler takes \(\mathit{GR}\) and the per-function relaxed permission sets \(\mathcal{R}^{*}_f\) as inputs and assembles them into IAM policy statements \(P_N\), grouping by function and then by resource. For each resource accessed by a function, a single policy statement is generated that assigns the corresponding permissions. For instance, In an AWS serverless application, if a function issues three requests \(r_1, r_2, r_3\) where \(r_1\) and \(r_3\) are read and write requests to access an AWS S3 resource bucket/images/ and \(r_2\) is a read request to resource bucket/videos/, the Policy Compiler generates:
iamRoleStatements:
- Effect: "Allow"
Action: ["s3:GetObject", "s3:PutObject"]
Resource: "bucket/images/"
- Effect: "Allow"
Action: ["s3:GetObject"]
Resource: "bucket/videos/"
The aggregated policy \(P_N\) corresponds to \(\bigcup_f \mathcal{R}^{*}_f\) and is compared against the developer-defined grant set \(\bigcup_f \mathcal{G}_f\) to quantify over-privilege as \(\bigl(\bigcup_f \mathcal{G}_f\bigr) \setminus \bigl(\bigcup_f \mathcal{R}^{*}_f\bigr)\): permissions granted but never required by the application.
We run PrivLess on our dataset on a virtual machine with a 48-core AMD EPYC 7643 processor, 16 GB of memory, and 285 GB of disk space, running Ubuntu 22.04.5 LTS.
Across all 789 analyzed applications, global-only IAM policies dramatically dominate at 88.6% (699 apps), revealing that developers overwhelmingly default to granting a single permission set across all functions, which inadvertently increases the blast radius of the application. This vulnerability was seen in breaches like the Capital One breach [19], where a compromised function accessed S3 buckets it was not supposed to access. On the other hand, only 6.6% (52 apps) followed the recommended approach for least-privilege enforcement by defining function-based IAM policies. We show in later analysis that even these scoped policies often are similarly overprivileged. Also, a small fraction (2.4%, 19 apps) defined both global and function-based policies, which can be ideal for scenarios where all functions require similar access on some resources, in addition to other scoped permissions. Finally, there were some 19 apps (2.4%) that relied on CloudFormation default roles outside the application’s control, making them invisible to our static analysis pipeline.
Wildcard usage in permissions represents one of the most serious forms of over-privilege in IAM policies. In our dataset, 23.3% (184 apps) included at least one wildcard permission, indicating widespread misuse of this dangerous pattern. Table 3 breaks down the severity distribution.
The most critical findings are the 10 applications (5.4% of 184) using full wildcards (*), which grant unrestricted access to all AWS services and resources. Compromise of any function in these applications amounts to near-complete account
takeover: an attacker could create IAM users, exfiltrate data across all services, or establish persistent backdoors with no technical constraints. This represents a fundamental break of the security model.
More prevalent are the 163 applications (88.6%) employing service-level wildcards (<service>:*), which allows all actions within specific services (e.g., S3, DynamoDB). While more limited than full wildcards, these still violate
least-privilege principles by granting unnecessary permissions within each service. A function that only reads S3 configuration should never have permissions to delete buckets, modify ACLs, or make data public—yet service wildcards grant all of these
capabilities.
Finally, 20 applications (10.9%) used prefix wildcards (<service>:<prefix>*), such as dynamodb:Batch*, granting groups of related actions when functions typically need only one or two specific operations. While the
blast radius is smaller, this pattern still demonstrates inadequate permission governance.
| Wildcard Type | Permissions | Resources |
|---|---|---|
| Full | 10 (5.4%) | 354 (56.2%) |
| Service | 184 (88.6%) | 154 (24.4%) |
| Prefix | 15 (10.9%) | 253 (40.2%) |
| Total | 184 | 630 |
6pt
Resource wildcards compound the over-privilege problem. In our study, 79.8% (630 apps) used wildcards in their resource definitions, demonstrating how action-level permissions are amplified by overly broad resource scopes (Table 3). Most critically, 354 applications (56.2% of 630) used full resource wildcards (Resource: "*"), allowing functions to access any resource in the account regardless of ownership or purpose. The
consequences are severe. A policy granting s3:GetObject on Resource: "*" permits functions to read every S3 bucket in the account, including those containing credentials, application secrets, customer PII, and data from other
applications. A single compromised function can thus trigger account-wide data exfiltration. This exact pattern enabled the Capital One breach [19],
where wildcard resources exposed more than 700 S3 buckets and sensitive customer data.
Service-scoped resource wildcards appear in 24.4% (154 apps), such as Resource: "arn:aws:dynamodb:region:account:table/*". While nominally limiting access to dynamoDB service resources, these still grant unrestricted access to all dynamoDB
tables in a tenant’s directory, including production databases and backups from other applications.
Prefix wildcards, used by 40.2% (253 apps), represent a more disciplined approach—for example, Resource: "arn:aws:dynamodb:region:account:table/app-*" scopes access to tables matching a naming convention. However, these can still enable
over-privilege if the prefix patterns are too broad or inadvertently span unrelated resources. Even with careful naming, prefix wildcards remain a compromise between convenience and principle.
Over-privilege reaches its peak when permission and resource wildcards combine (compounded over-privilege), amplifying the attack surface multiplicatively. In our dataset, 21% (166 apps) exhibited this dangerous combination. Most alarmingly, 7
applications configured the worst possible scenario: full permission wildcards (*) paired with full resource wildcards (Resource: "*"), granting functions unrestricted access to the entire AWS account. Against such configurations,
even trivial vulnerabilities such as an SQL injection flaw, an SSRF bug, or a compromised dependency, become account-takeover vectors. An attacker could read all data across all services, create privileged IAM credentials, delete production databases, or
pivot to other AWS accounts via cross-account roles with no technical barriers.
Beyond these extreme cases, partial wildcard combinations remain highly dangerous. A policy that combines service-level action wildcards (e.g., s3:*) with full resource wildcards (Resource: "*") grants all S3 operations across
all buckets, enabling massive data exfiltration. Similarly, prefix action wildcards (e.g., dynamodb:Batch*) paired with full resource wildcards permit all batch operations on every DynamoDB table. Even seemingly "reasonable" combinations of
service-scoped resources with broad action wildcards remove the containment boundaries that should limit the blast radius of a compromised function. Table 4 enumerates the distribution of these dangerous
co-occurrence patterns across our dataset.
| Wildcard Combination | Count(%) | |
|---|---|---|
| Full Action + Full Resource | 7 (4%) | |
| Full Action + Service Resource | 1 (0.6%) | |
| Full Action + Prefix Resource | 2 (1.2%) | |
| Service Action + Full Resource | 114 (68.7%) | |
| Service Action + Service Resource | 25 (15.1%) | |
| Service Action + Prefix Resource | 57 (34.3%) | |
| Prefix Action + Full Resource | 16 (9.6%) | |
| Prefix Action + Service Resource | 2 (1.2%) | |
| Prefix Action + Prefix Resource | 8 (4.8%) |
6pt
We find that wildcard usage concentrates most heavily on services that store sensitive data, amplifying data breach risks. As shown in Figure 4, S3 and DynamoDB, the primary data storage services in AWS serverless architectures, exhibit the highest wildcard usage with 312 applications defining resource wildcards and 128 applications using permission wildcards across these services.
We explored the attack surface and blast radius in the event of a function compromise. Using a subset of the MITRE ATT&CK framework [20], we use
heuristics (Appendix 6.3) to classify permissions into 8 categories: Reconnaissance (e.g, iam:ListUsers), Data Exfiltration (e.g., rds:DownloadDBLogFilePortion),
Credential Access (e.g., iam:CreateAccessKey), Privilege Escalation (e.g., sts:AssumeRole), Data Tampering (e.g. s3:PutObject), Data Destruction (e.g., dynamodb:DeleteTable),
Denial of Service (e.g., ec2:TerminateInstances), Resource Hijacking (e.g., batch:SubmitJob) and Defense Evasion (e.g., cloudtrail:stopLogging).
We observe that many applications have permissions that can enable full attack chains. Data Tampering permissions, which allow attackers to change records, add harmful content, or alter how the app works, are found in 76.6% of applications. Data Exfiltration permissions are present in 59.9% of applications. 41.3% of applications have Data Destruction permissions that could be used for ransomware attacks or sabotage, and 21.4% have Privilege Escalation permissions that allow attackers to assume higher-privileged roles. Credential Access permissions, which allow attackers to create persistent backdoors using new API keys or session tokens, are found in 9.0% of applications. This spread of permissions means that if a function is compromised, attackers often do not need to use multiple exploits as they already have what they need to carry out multi-stage attacks. These can include mapping resources, stealing data, maintaining access, and hiding their actions. The high number of applications with destructive permissions (41.3% can delete data) is especially worrying, as it means nearly half of serverless applications could be used to destroy data or mount denial-of-service attacks if any function is compromised.
Figure 6: Cumulative density of permissions required against density of permissions granted per application. 76.7% of applications require four or fewer permissions to function. However, 65.3% are granted five or more. a — Granted Permissions Density, b — Required Permissions Density
Figure 7: Cumulative density of permissions required against cumulative density of permissions granted per function. 46.7% of the applications require two or fewer permissions. However, 87.7% were granted three or more permissions. a — Granted Permissions Density, b — Required Permissions Density
We measured the gap between permissions granted in developer-defined IAM policies and the minimal set PrivLess infers from function interactions. Across the 789 applications, we observe a striking disparity between granted and required permissions. Developers collectively granted 538,390 effective permissions (after wildcard expansion) against 1,896 required permissions determined by PrivLess. This represents a staggering 99.65% privilege reduction potential. The mean granted was 683.37 versus 2.40 required (285\(\times\)), with the disparity holding at the median where applications were granted 9 permissions while requiring only 1. This shows that over-privilege is not limited to outliers but is a systemic issue. In total, 47.7% of applications (376) carry more permissions than PrivLess determines are necessary. The CDF in Figure 6 illustrates the systematic mismatch: 76.7% of applications require four or fewer permissions to function, yet 65.3% are granted five or more. Only 26.1% of applications receive permissions closely matching their actual needs. Moreover, at the function level (Figure 7), 46.7% of functions required two or fewer permissions to function but 87.7% were granted more, demonstrating that permission grants are decoupled from code requirements.
Wildcards significantly amplify over-privilege. As shown in Table 5, applications with wildcards exhibit a 98.69% privilege reduction potential compared to 61.78% for those without, and are over-privileged at higher rates (59.2% vs. 44.1%). This reflects how unrestricted action patterns compound permission sprawl.
Similarly, policy scope choice directly influences overprivilege severity(Table 5). Global-only policies, used in 699 applications (88.6%), had a 73.97% privilege reduction potential. Function-level policies, though used by only 52 applications, demonstrate substantially lower over-privilege with a 41.35% reduction potential. This affirms that function-based policies minimize the blast radius, but do not automatically eliminate overprivilegr and require careful privilege assignment. Hybrid policies (both global and function-level) split the difference at 88.20% reduction, suggesting that per-function refinement, even when paired with a global baseline, meaningfully constrains excess permissions. The dominance of global-only policies thus directly drives the systemic over-privilege observed across the dataset.
| Category | Apps | Avg Granted | Reduction |
|---|---|---|---|
| No Wildcards | 605 | 14.72 | 61.78% |
| Has Wildcards | 184 | 2,877.62 | 98.69% |
| Global Only | 699 | 738.89 | 73.97% |
| Function Only | 52 | 23.23 | 41.35% |
| Both | 19 | 1,089.47 | 88.20% |
6pt
We classified all granted and required IAM permissions into nine threat categories (Reconnaissance, Data Exfiltration, Credential Access, Privilege Escalation, Data Tampering, Data Destruction, DoS, Resource Hijacking, Defense Evasion) to assess real-world risk. The granted vs. required gap is stark: Reconnaissance permissions appear in 48.8% of applications despite rarely being functionally necessary; Privilege Escalation permissions are granted in 93 applications (18.8%) but required in only 19; most critically, Defense Evasion permissions (e.g., disabling CloudTrail, deleting CloudWatch alarms) are granted in 12 applications yet required by zero, creating silent persistence paths for attackers. Only Credential Access shows restrained over-granting (77.0% reduction), suggesting developers exercise discipline when managing secrets but not across other threat categories.
For the end-to-end runtime of our implementation of PrivLess, we observed a mean runtime of 193.6 seconds across all 789 applications. CodeQL operations dominate with 99.9% of total execution time consumed by database creation (\(\sim24\%\)) and query execution (\(\sim75\%\)). The core Python-side analysis (resource resolution, call-graph scoping, and permission mapping) completes in under 100 ms on average and is never a bottleneck. We noticed a high variance in runtime (p95 \(\approx\) 8 min) which reflects differences in codebase size, dependency depth, and language complexity (e.g., JS/TS transpilation), not PrivLess’s algorithm. We argue that PrivLess’s analysis is not in the critical path and operates offline; hence, the seemingly high end-to-end time cost is acceptable. Moreover, alternative static analysis tools (such as pure AST-based analyzers) could eliminate database creation overhead and reduce runtime. We chose CodeQL for its rich query language and relational database semantics, a performance tradeoff justified by enhanced analysis capability and precision.
To ground the aggregate findings, we examine five applications that collectively span the dominant patterns of over-privilege observed in our dataset (Table 6; per-function breakdowns in Appendix 6.2). For ethical reasons, we anonymized the case-study apps.
The five applications exhibit four anti-patterns: over-scoped service wildcards, where s3:* (229 actions) and rekognition:* (76 actions) are applied globally in Anonymized-1 (3,110 effective permissions)
when one specific action per service suffices; speculative service inclusion (service suites declared for integrations never implemented or removed, as in Anonymized-2’s unused Rekognition suite — 126 grants, zero calls);
deployment tooling in runtime roles (codedeploy:* alone accounts for 517 of 570 grants in Anonymized-4); and over-declared operations (a global policy in the Anonymized-3&5 app grants all
anticipated actions uniformly to every function regardless of individual need). Reductions range from 83% to 99.8%, showing that over-privilege scales with policy scope rather than application complexity.
| App | Funcs | Granted | Required | Reduction | |
|---|---|---|---|---|---|
| Anonymized-1 | 10 | 3,110 | 7 | 99.8% | |
| Anonymized-2 | 18 | 522 | 22 | 95.8% | |
| Anonymized-3 | 20 | 82 | 11 | 86.6% | |
| Anonymized-4 | 11 | 570 | 5 | 99.1% | |
| Anonymized-5 | 5 | 30 | 5 | 83.3% |
5pt
Cloud IAM Auditing Tools: AWS Zelkova[21], [22] uses SMT solving to evaluate policy conditions, while AWS IAM Access Analyzer[23], Azure Monitor[24], and Google Security Command Center[25] provide post-deployment auditing and visibility. These tools are reactive—reporting overprivilege after deployment. PrivLess enables pre-deployment measurement of the permission gap at scale by analyzing source code directly.
Information Flow Control: Systems like Valve [26], Trapeze Trapeze?, and SecLambda [27] enforce data confidentiality in serverless applications but assume IAM policies are already correctly scoped. PrivLess addresses the upstream problem: quantifying the extent to which real-world policies exceed what functions actually require.
Static Policy Analysis: GRASP[1] constructs reachability graphs from configurations, AutoArmor[28] analyzes microservice patterns, and Will.IAM[29] enforces access control dynamically. These rely on configuration correctness or runtime instrumentation. ALPS[30], the closest related work, applies static analysis to infer minimum permissions and enforces them via runtime hooks across multiple cloud providers. Unlike ALPS, which targets enforcement, PrivLess targets measurement—quantifying overprivilege in large real-world corpora without code modification or runtime instrumentation. Growlithe[31] combines static and runtime approaches but requires policy annotations. D’Antoni et al.[32] infer policies from request logs, but log-based approaches miss untriggered code paths.
We present a systematic overprivilege analysis framework, PrivLess, that quantifies the gap(overprivilege) between developer-defined IAM policies and actual applications’ IAM needs. Applied to 789 real-world serverless applications, our study establishes that overprivilege is endemic: 47.7% of applications carry excess permissions with an aggregate privilege reduction potential of upto 99.65%. Wildcard policies are the primary driver, inflating overprivilege to 799\(\times\) for affected applications (274\(\times\) higher than those without wildcards). These excess permissions pose concrete risks, as 18.8% of applications hold unnecessary privilege escalation capabilities, and 12 applications grant defense evasion permissions they never use. These findings demonstrate that manual IAM management has systematically failed in serverless environments, making automated analysis not merely beneficial but essential infrastructure for understanding and remediating overprivilege at scale.
Our dataset consists of publicly available open-source repositories. While this data is publicly accessible, our analysis reveals security vulnerabilities (overprivileged policies) that developers may not have been aware of. To respect developer privacy and intent, we anonymize applications in aggregate findings and encourage repository maintainers to use PrivLess to audit their own applications.
The five applications selected for detailed case studies are currently anonymized in this manuscript. We have conducted responsible disclosure by directly emailing repository owners with detailed findings, security implications, and remediation recommendations. We deliberately refrained from using public channels (pull requests or GitHub issues) for disclosure, as publicly exposing overprivilege vulnerabilities could enable attackers to exploit these weaknesses before fixes are deployed. This private disclosure approach allows developers to address issues on their own timeline without creating attack opportunities. If repository owners provide feedback, explicit consent, and evidence of fixes before publication, we commit to de-anonymizing those applications and crediting the maintainers for their proactive response. This practice balances transparency, security, and developer agency.
is a code-sharing platform that allows users to create, view, update, and delete code snippets, with image upload support. Its 10 Lambda functions are: verify (domain ownership verification), main (homepage),
features (feature listing), create (snippet creation), login (WeChat OAuth), userCode (list snippets by user), code (get snippet detail), updateCode (edit snippet),
deleteCode (remove snippet), and upload (image upload with text recognition).
The global IAM policy carries three statements applied uniformly to all 10 functions: six specific DynamoDB actions (GetItem, PutItem, UpdateItem, Query, Scan, DeleteItem) on
the application table; s3:* on the upload bucket; and rekognition:* on all resources. After wildcard expansion using the permission model (229 S3 actions, 76 Rekognition actions), the effective total is \(3{,}110\) permissions across the 10 functions.
PrivLess detects service calls in 6 of the 10 functions. The CRUD handlers (code, userCode, updateCode, deleteCode, create) use four DynamoDB operations (Scan,
PutItem, UpdateItem, DeleteItem); the relaxer augments GetItem and Query back since both appear in the developer policy and DynamoDB is a directly-detected service. The upload
handler uses two services: s3:PutObject (direct call) and rekognition:DetectText, which is called through the local utility module imageAnalyser.js using Rekognition’s nested image schema
({Image:{S3Object:{Bucket,Name}}}). The extractor recognises Image as a resource parameter and correctly attributes the call to upload. The inferred minimum policy requires 8 unique permissions — six DynamoDB actions,
s3:PutObject, and rekognition:DetectText — yielding a 99.7% reduction (\(3{,}110\) to 8).
The case illustrates how service-level wildcards compound over-privilege across two dimensions. The s3:* wildcard grants all 229 S3 actions to every function when only upload needs PutObject;
rekognition:* grants all 76 Rekognition actions globally for what is ultimately a single DetectText call in one function. In both cases PrivLess identifies the precise minimal action and produces per-function policies that
eliminate the wildcard entirely.
is a photo-management platform that stores images in S3, manages user metadata in DynamoDB, and identifies faces using Amazon Rekognition. It has 18 Lambda functions that handle the full lifecycle: create, get,
query, update, delete, thumbs, personThumb, fotoEvents, indexes, indexesUpdate, faces, people, peopleUpdate,
peopleMerge, person, stream, collectionCreate, and collectionDelete.
The global IAM policy lists 29 permission entries applied uniformly to all 18 functions — 12 DynamoDB operations, four S3 actions across two buckets, two Lambda actions, four CloudWatch Logs actions, and a seven-action Rekognition suite
(IndexFaces, CreateCollection, DeleteCollection, DeleteFaces, DetectLabels, ListFaces, SearchFaces) — totalling 522 declared permissions across all functions. No
wildcards appear, so effective equals declared.
PrivLess traces SDK calls across all 18 functions and detects no code path that invokes any Rekognition action: the seven-action suite (126 grants across 18 functions) exists entirely as policy bloat. The application’s authentication module uses the Laconia dependency-injection framework, which passes AWS SDK client objects as callback parameters; this pattern is opaque to static analysis, so Cognito calls in the auth layer are not attributed to Lambda handlers. Since the developer’s IAM policy likewise grants no Cognito permissions to the Lambda execution role, this analysis gap does not affect the output. And because the app uses a single global IAM policy, the relaxer assigns the same 22-permission set (the developer’s 29 actions minus the seven Rekognition actions) to every function, yielding a \(24\times\) ratio and 95.8% reduction potential (522 to 22 unique permissions).
is a Twitter-clone social backend built on AWS AppSync and DynamoDB. Unlike the previous applications, it assigns function-level IAM policies to each of its 20 Lambda functions, which implement user management (confirmUserSignup,
getImageUploadUrl), social graph operations (tweet, retweet, unretweet, reply), follower distribution (distributeTweets, distributeTweetsToFollower), search and
discovery (search, getHashTag, syncUsersToAlgolia, syncTweetsToAlgolia), notifications (notify, notifyLiked, notifyDmed), messaging (sendDirectMessage,
getTweetCreator), and infrastructure management (firehoseTransformer, setResolverLogLevelToAll, setResolverLogLevelToError). Most functions are granted individual DynamoDB operations against named table
ARNs; search-related functions additionally receive ssm:GetParameters for Algolia credentials; and a global policy applies xray:PutTraceSegments and xray:PutTelemetryRecords to all 20 functions. After summing across
all functions, the developer declares 82 total effective permissions.
PrivLess identifies 11 unique actions in actual use: six DynamoDB operations (GetItem, PutItem, UpdateItem, DeleteItem, Query, BatchWriteItem), two S3 actions
(PutObject, PutObjectAcl), ssm:GetParameters, iam:PassRole, and dynamodb:BatchGetItem. The two globally declared X-Ray actions do not appear in any application code path; the relaxer evicts
them from the generated policy. The result is a \(7.5\times\) ratio and 86.6% reduction (82 to 11 unique permissions). This case shows that function-level policies substantially narrow the blast radius compared to global
grants, but excess still accumulates through operational tooling (X-Ray) declared speculatively across all functions.
is a restaurant ordering application comprising 11 functions: get-index, get-restaurants, search-restaurants, place-order, notify-restaurant, retry-notify-restaurant,
accept-order, notify-user, retry-notify-user, auto-create-api-alarms, and fulfill-order.
Its global IAM role carries three entries: cloudwatch:PutMetricData, the X-Ray pair, and codedeploy:*. The last is the primary driver of over-privilege: the CodeDeploy service-level wildcard expands to 47 runtime permissions
and is applied to all 11 functions, contributing 517 of the 570 total effective permissions. The intent is to support blue/green deployment automation, but CodeDeploy actions are invoked by deployment pipelines, not by Lambda function code at runtime — no
function ever calls a CodeDeploy API. Function-level overrides add targeted permissions: dynamodb:scan (restaurants table), kinesis:PutRecord (order events), sns:Publish (notification topics),
execute-api:Invoke, ssm:GetParameters* (prefix wildcard expanding to 2 SSM actions), and secretsmanager:GetSecretValue.
PrivLess traces SDK calls and identifies five unique actions across the 11 functions: cloudwatch:PutMetricData (all functions), cloudwatch:PutMetricAlarm (auto-create-api-alarms only),
kinesis:PutRecord (order-processing functions), sns:Publish (notification functions), and dynamodb:Scan (restaurant-query functions). Although Cognito calls appear in test-helper files, the developer’s runtime IAM
policy does not include Cognito permissions for Lambda; the relaxer correctly evicts those calls, excluding test infrastructure from the generated function policies. No code path invokes CodeDeploy at runtime, confirming that the wildcard’s 47-action
expansion (517 of the 570 total grants) is pure overhead. The inferred minimum policy requires five permissions — none from CodeDeploy or X-Ray — yielding a \(114\times\) ratio and 99.1% reduction (570 to 5 unique
permissions). This case illustrates how deployment tooling bundled into runtime IAM roles produces systematic over-privilege across every function in the service.
is a canonical Serverless Framework starter for a CRUD REST API backed by DynamoDB, comprising five Lambda functions each responsible for a single operation: create (POST), list (GET all), get (GET by id),
update (PUT), and delete (DELETE). The developer declares a single global IAM policy granting six DynamoDB actions — Query, Scan, GetItem, PutItem, UpdateItem, and
DeleteItem — on the application table, which is pplied uniformly to all five functions. With no wildcards, the effective permissions equal declared permissions: 30 total across the functions.
PrivLess traces one SDK call per function and identifies five distinct DynamoDB actions in use: PutItem (create), Scan (list), GetItem (get), UpdateItem
(update), and DeleteItem (delete). No function calls query, making dynamodb:Query the sole superfluous permission — granted to all five functions but exercised by none. The inferred minimum
policy requires five unique permissions, yielding a \(6\times\) ratio and 83.3% reduction (30 to 5 unique permissions). Beyond service-level eviction, per-function scoping reveals a deeper opportunity: each function needs
exactly one DynamoDB action, yet the global policy grants six to all. PrivLess’s per-function output assigns each function its single required action, reducing total grants from 30 to 5 and eliminating cross-function privilege (a create
function that also holds DeleteItem can silently delete records). This case establishes the baseline pattern — over-declared operations in a global policy — that the following applications exhibit at larger scale.
We classify AWS IAM permissions using a priority-ordered set of heuristic rules that match keyword patterns. Classification groups are:
Reconnaissance (R), Data Exfiltration (DE), Data Destruction(DD), Credential Access (CA), Privilege Escalation (PE), Data Tampering (DT), Denial of Service (DoS), Resource Hijacking (RH) and Defense Evasion(DEv). Permissions that
were not classified were added as classified as Other. Given a permission string \(p\) with extracted service \(s\) and action \(a\), we apply
the following classification rules in order.
First, we define an override function \(\text{Override, } \mathcal{O}(s, a)\) that checks for explicit service-action mappings for overriding general rules:
\[\begin{align} \mathcal{O}(s, a) = R &\text{ if } (s{=}\text{iam}) \land (a{\in}\{\text{get, list}\}) \nonumber \\ \mathcal{O}(s, a) = R &\text{ if } (s{=}\text{sm}) \land (a{\in}\{\text{desc, list}\}) \nonumber \\ \mathcal{O}(s, a) = R &\text{ if } (s{=}\text{ssm}) \land (a{\in}\{\text{desc, list}\}) \nonumber \\ \mathcal{O}(s, a) = R &\text{ if } (s{=}\text{kms}) \land (a{\in}\{\text{get, list}\}) \nonumber \\ \mathcal{O}(s, a) = PE &\text{ if } (s{=}\text{sts}) \land (a{\in}\{\text{assume, get}\}) \nonumber \\ \mathcal{O}(s, a) = PE &\text{ if } (s{=}\text{lam}) \land (a{\in}\{\text{upd, crt}\}) \nonumber \\ \mathcal{O}(s, a) = CA &\text{ if } (s{=}\text{sm}) \land (a{=}\text{get}) \nonumber \\ \mathcal{O}(s, a) = CA &\text{ if } (s{=}\text{ssm}) \land (a{=}\text{get}) \nonumber \\ \mathcal{O}(s, a) = CA &\text{ if } (s{=}\text{kms}) \land (a{=}\text{dec}) \nonumber \\ \mathcal{O}(s, a) = RH &\text{ if } (s{=}\text{lam}) \land (a{=}\text{inv}) \nonumber \\ \mathcal{O}(s, a) = DEv &\text{ if } (s{=}\text{ct}) \land (a{\in}\{\text{stp, del}\}) \nonumber \\ \mathcal{O}(s, a) = DEv &\text{ if } (s{=}\text{gd}) \land (a{=}\text{del}) \nonumber \\ \mathcal{O}(s, a) = DEv &\text{ if } (s{=}\text{lg}) \land (a{=}\text{del}) \end{align}\]
Abbreviations: R = Reconnaissance, PE = Privilege Escalation, CA = Credential Access, RH = Resource Hijacking, DEv = Defense Evasion; sm = secretsmanager, lam = lambda, ct = cloudtrail, gd = guardduty, lg = logs; desc = describe, upd = update, crt = create, inv = invoke, dec = decrypt, stp = stop, del = delete.
If no override applies, we evaluate the following rules in priority order (stopping at the first match):
Credential Access (CA): \[\begin{align} CA(s, a) = & \Big[(s \in \mathcal{S}_{c}) \land (\exists \alpha \in \mathcal{A}_{r} : \alpha \subseteq a) \land \nonumber \\ & \quad (\nexists \lambda \in \mathcal{A}_{l} : \lambda \subseteq a)\Big] \lor \nonumber \\ & \Big[(\exists \kappa \in \mathcal{K}_{c} : \kappa \subseteq a) \land \nonumber \\ & \quad (\exists \alpha \in \mathcal{A}_{r} : \alpha \subseteq a)\Big] \end{align}\] where \(\mathcal{S}_{c} = \{\text{secretsmanager, ssm, kms}\}\),
\(\mathcal{A}_{r} = \{\text{get, decrypt, retrieve}\}\),
\(\mathcal{A}_{l} = \{\text{list, describe}\}\),
\(\mathcal{K}_{c} = \{\text{secret, password, key, token, credential,}\) \(\text{parameter}\}\)
Privilege Escalation (PE): \[\begin{align} PE(s, a) = & \Big[(s \in \mathcal{S}_{p}) \land (\exists \alpha \in \mathcal{A}_{m} : \alpha \subseteq a) \land \nonumber \\ & \quad (\exists \kappa \in \mathcal{K}_{p} : \kappa \subseteq a)\Big] \lor \nonumber \\ & \Big[(\text{passrole} \subseteq a') \lor (\text{assumerole} \subseteq a')\Big] \lor \nonumber \\ & \Big[(s = \text{lambda}) \land (\text{functioncode} \subseteq a)\Big] \end{align}\] where \(\mathcal{S}_{p} = \{\text{iam, sts, lambda, ec2}\}\),
\(\mathcal{A}_{m} = \{\text{attach, put, create, update, modify, add,}\) \(\text{associate}\}\),
\(\mathcal{K}_{p} = \{\text{policy, role, user, group, permission, assume,}\) \(\text{pass}\}\), \(a'\) denotes \(a\) with delimiters removed
Data Destruction (DD): \[\begin{align} DD(s, a) = & (\exists \alpha \in \mathcal{A}_{d} : \alpha \subseteq a) \land \nonumber \\ & \Big[(\exists \kappa \in \mathcal{K}_{d} : \kappa \subseteq a) \lor (s \in \mathcal{S}_{d})\Big] \end{align}\] where \(\mathcal{A}_{d} = \{\text{delete, remove, destroy, terminate, drop}\}\), \(\mathcal{K}_{d} = \{\text{object, item, record, table, bucket, function,}\) \(\text{instance, volume}\}\),
\(\mathcal{S}_{d} = \{\text{s3, dynamodb, rds, redshift, elasticache,}\) \(\text{documentdb}\}\)
Denial of Service (DoS): \[\begin{align} DoS(s, a) = & (\exists \alpha \in \mathcal{A}_{s} : \alpha \subseteq a) \land \nonumber \\ & (\exists \kappa \in \mathcal{K}_{s} : \kappa \subseteq a) \end{align}\] where \(\mathcal{A}_{s} = \{\text{delete, stop, terminate, disable}\}\),
\(\mathcal{K}_{s} = \{\text{function, instance, cluster, service, table,}\) \(\text{ distribution, loadbalancer, database, queue, topic}\}\)
Resource Hijacking (RH): \[\begin{align} RH(s, a) = & (s \in \mathcal{S}_{h}) \land (\exists \alpha \in \mathcal{A}_{e} : \alpha \subseteq a) \land \nonumber \\ & (\exists \kappa \in \mathcal{K}_{r} : \kappa \subseteq a) \end{align}\] where \(\mathcal{A}_{e} = \{\text{run, invoke, create, start, execute,}\) \(\text{launch}\}\),
\(\mathcal{S}_{h} = \{\text{ec2, lambda, sagemaker, batch, ecs, eks}\}\),
\(\mathcal{K}_{r} = \{\text{instance, function, job, task, training,}\) \(\text{container}\}\)
Data Exfiltration (DE): \[\begin{align} DE(s, a) = & (\exists \alpha \in \mathcal{A}_{r} : \alpha \subseteq a) \land \nonumber \\ & \Big[(\exists \kappa \in \mathcal{K}_{o} : \kappa \subseteq a) \lor (s \in \mathcal{S}_{d})\Big] \end{align}\] where \(\mathcal{A}_{r} = \{\text{get, read, download, export,}\) \(\text{retrieve, fetch, select}\}\),
\(\mathcal{S}_{d} = \{\text{s3, dynamodb, rds, redshift, elasticache}\}\)
\(\mathcal{K}_{o} = \{\text{object, item, record, file, document, data,}\) \(\text{content, body, backup}\}\),
Data Tampering (DT): \[\begin{align} DT(s, a) = & (\exists \alpha \in \mathcal{A}_{m} : \alpha \subseteq a) \land \neg PE(s, a) \end{align}\] where \(\mathcal{A}_{m} = \{\text{put, update, modify, write, edit,}\) \(\text{ change, replace, upload, insert}\}\)
Reconnaissance (R): \[\begin{align} R(s, a) = & (\exists \alpha \in \mathcal{A}_{l} : \alpha \subseteq a) \land \nonumber \\ & (\nexists \kappa \in \mathcal{K}_{c} : \kappa \subseteq a) \land \nonumber \\ & (\nexists \kappa \in \mathcal{K}_{o} : \kappa \subseteq a) \end{align}\] where \(\mathcal{A}_{l} = \{\text{list, describe, get, scan, query, search}\}\), \(\mathcal{K}_{c}\) and \(\mathcal{K}_{o}\) as defined above
Other (O): Default category if no rule matches.
The final classification is determined by: \[\text{Classify}(p) = \begin{cases} c & \text{if } \exists c \in \mathcal{O}(s, a) \\ c_i & \text{if } c_i \text{ is first true in priority order} \\ \text{Other} & \text{otherwise} \end{cases}\]
Notation: \(\subseteq\) denotes substring containment (e.g., “get” \(\subseteq\) “getobject”); \(\mathcal{S}\), \(\mathcal{A}\), and \(\mathcal{K}\) denote service, action, and keyword pattern sets respectively; \(\exists\) and \(\nexists\) denote existence and non-existence quantifiers.
service-2.json) encode operation names, parameter shapes, and embedded documentation.“Botocore: Low-level, data-driven core of the
AWS SDK for Python.” https://github.com/boto/botocore, 2024.aws.iam trait namespace provides machine-readable IAM permission annotations including requiredActions and
conditionKeys.“Smithy: A language for defining services and SDKs.” https://smithy.io/, 2024.z0ph/MAMIP.“aws_managed_policies: A collection of all AWS managed IAM policies.” https://github.com/SummitRoute/aws_managed_policies, 2024.