Back to Checks
aws

Regular SageMaker access ensures IAM users retain only actively used permissions

iam_user_access_not_stale_to_sagemaker
Service: iamSeverity: medium
OfficialBuilt-inVerified
Categories:identity-access
Resource Type:AwsIamUser

IAM users granted SageMaker permissions are evaluated for recent service usage.

Users whose last SageMaker access exceeds the configured threshold (default 90 days) or that have never accessed SageMaker are flagged, indicating stale permissions that should be reviewed.

Risk

Stale SageMaker permissions widen the blast radius of a credential compromise.

An attacker who gains access to a user with unused SageMaker permissions can access ML training data, models, endpoints, and notebooks — all without triggering expected usage patterns. Removing or scoping down stale permissions enforces least privilege and limits blast radius.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks iam_user_access_not_stale_to_sagemaker

Recommendation

Apply the principle of least privilege by regularly reviewing IAM Access Advisor data and revoking SageMaker permissions that are no longer actively used.

Establish a periodic access review process and automate alerts for stale permissions to maintain a minimal attack surface.

Remediation

Other

1Open the IAM console and select the user
2Review the **Access Advisor** tab to confirm SageMaker has not been accessed recently
3Remove or detach any policies granting SageMaker permissions that are no longer needed
4If the user still requires SageMaker access, verify usage and reduce scope to least privilege

Check Code

from datetime import datetime, timezone
from typing import Optional

from dateutil.parser import parse

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.iam_client import iam_client


class iam_user_access_not_stale_to_sagemaker(Check):
    """Detect IAM users with stale SageMaker permissions.

    This check evaluates whether IAM users with SageMaker service permissions
    have actively used those permissions within the configured threshold
    (default 90 days).

    - PASS: The user has accessed SageMaker within the allowed period.
    - FAIL: The user has SageMaker permissions but has not used them within
      the allowed period or has never used them.
    """

    def execute(self) -> list[Check_Report_AWS]:
        """Execute the SageMaker access staleness check for IAM users.

        Iterates over IAM users, inspecting service last accessed data for
        the ``sagemaker`` namespace. Users whose last SageMaker access exceeds
        the configured threshold are reported as non-compliant.

        Returns:
            A list of reports containing the result of the check.
        """
        findings = []
        max_unused_sagemaker_days = iam_client.audit_config.get(
            "max_unused_sagemaker_access_days", 90
        )

        for user in iam_client.users:
            last_accessed_services = iam_client.last_accessed_services.get(
                (user.name, user.arn), []
            )
            sagemaker_service = self._find_sagemaker_service(last_accessed_services)
            if sagemaker_service is None:
                continue

            report = Check_Report_AWS(metadata=self.metadata(), resource=user)
            report.region = iam_client.region

            self._evaluate_sagemaker_staleness(
                report,
                sagemaker_service,
                max_unused_sagemaker_days,
                user.name,
                "User",
            )
            findings.append(report)

        return findings

    @staticmethod
    def _find_sagemaker_service(
        last_accessed_services: list[dict],
    ) -> Optional[dict]:
        """Return the SageMaker entry from a service last accessed list."""
        for service in last_accessed_services:
            if service.get("ServiceNamespace") == "sagemaker":
                return service
        return None

    @staticmethod
    def _evaluate_sagemaker_staleness(
        report: Check_Report_AWS,
        sagemaker_service: dict,
        max_days: int,
        identity_name: str,
        identity_type: str,
    ) -> None:
        """Populate a check report based on SageMaker access recency."""
        last_authenticated = sagemaker_service.get("LastAuthenticated")
        if last_authenticated is None:
            report.status = "FAIL"
            report.status_extended = (
                f"IAM {identity_type} {identity_name} has SageMaker permissions "
                f"but has never used them."
            )
            return

        if isinstance(last_authenticated, str):
            last_authenticated = parse(last_authenticated)

        days_since_access = (datetime.now(timezone.utc) - last_authenticated).days

        if days_since_access > max_days:
            report.status = "FAIL"
            report.status_extended = (
                f"IAM {identity_type} {identity_name} has not accessed SageMaker "
                f"in {days_since_access} days (threshold: {max_days} days)."
            )
        else:
            report.status = "PASS"
            report.status_extended = (
                f"IAM {identity_type} {identity_name} accessed SageMaker "
                f"{days_since_access} days ago (threshold: {max_days} days)."
            )