{"record":{"id":"846efaae8e3eb645","repo":"infiniflow/ragflow","slug":"insufficient-permissions-to-list-objects-in-bucket","errorCode":null,"errorMessage":"Insufficient permissions to list objects in bucket '{self.bucket_name}'. Please check your bucket policy and/or IAM policy.","messagePattern":"Insufficient permissions to list objects in bucket '(.+?)'\\. Please check your bucket policy and/or IAM policy\\.","errorType":"validation","errorClass":"InsufficientPermissionsError","httpStatus":403,"severity":"error","filePath":"common/data_source/blob_connector.py","lineNumber":333,"sourceCode":"        if not self.bucket_name:\n            raise ConnectorValidationError(\"No bucket name was provided in connector settings.\")\n\n        try:\n            # Lightweight validation step\n            self.s3_client.list_objects_v2(Bucket=self.bucket_name, Prefix=self.prefix, MaxKeys=1)\n\n        except Exception as e:\n            error_code = getattr(e, \"response\", {}).get(\"Error\", {}).get(\"Code\", \"\")\n            status_code = getattr(e, \"response\", {}).get(\"ResponseMetadata\", {}).get(\"HTTPStatusCode\")\n\n            # Common S3 error scenarios\n            if error_code in [\n                \"AccessDenied\",\n                \"InvalidAccessKeyId\",\n                \"SignatureDoesNotMatch\",\n            ]:\n                if status_code == 403 or error_code == \"AccessDenied\":\n                    raise InsufficientPermissionsError(f\"Insufficient permissions to list objects in bucket '{self.bucket_name}'. Please check your bucket policy and/or IAM policy.\")\n                if status_code == 401 or error_code == \"SignatureDoesNotMatch\":\n                    raise CredentialExpiredError(\"Provided blob storage credentials appear invalid or expired.\")\n\n                raise CredentialExpiredError(f\"Credential issue encountered ({error_code}).\")\n\n            if error_code == \"NoSuchBucket\" or status_code == 404:\n                raise ConnectorValidationError(f\"Bucket '{self.bucket_name}' does not exist or cannot be found.\")\n\n            raise ConnectorValidationError(f\"Unexpected S3 client error (code={error_code}, status={status_code}): {e}\")\n\n\nif __name__ == \"__main__\":\n    # Example usage\n    credentials_dict = {\n        \"aws_access_key_id\": os.environ.get(\"AWS_ACCESS_KEY_ID\"),\n        \"aws_secret_access_key\": os.environ.get(\"AWS_SECRET_ACCESS_KEY\"),\n    }\n","sourceCodeStart":315,"sourceCodeEnd":351,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/common/data_source/blob_connector.py#L315-L351","documentation":"Raised by BlobStorageConnector.validate_connector_settings when the list_objects_v2 probe fails with error code AccessDenied, InvalidAccessKeyId, or SignatureDoesNotMatch AND (status 403 or code == 'AccessDenied'). This branch means the credentials authenticated but the identity lacks s3:ListBucket permission on the bucket, so InsufficientPermissionsError is raised with guidance to check bucket and IAM policy.","triggerScenarios":"An IAM user/role with GetObject but no ListBucket permission running validate_connector_settings; a bucket policy that explicitly denies s3:ListBucket for the caller's principal; an SCP or VPC endpoint policy restricting ListBucket; also AccessDenied from S3-compatible providers when the key is scoped to a different bucket.","commonSituations":"Least-privilege IAM keys created for read/download only; bucket owned by another AWS account with a bucket policy missing a ListBucket statement for the caller; MinIO/Ops role without read:list access; confusion between ListBucket (bucket-level) and GetObject (object-level) permissions.","solutions":["Add s3:ListBucket on the specific bucket (and s3:GetObject on bucket/*) to the IAM policy of the key's owner","If the bucket is in another account, add a bucket-policy statement allowing s3:ListBucket for the caller's ARN","Check for explicit Deny statements in IAM, bucket policy, SCPs, or VPC endpoint policies that override the Allow","For S3-compatible stores, grant the key's policy the listing permission on the bucket (e.g. MinIO 'readlist' or console 'readwrite' scoped policy)"],"exampleFix":"# before (IAM policy — download only)\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\"s3:GetObject\"],\n  \"Resource\": [\"arn:aws:s3:::docs-bucket/*\"]\n}\n# after\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\"s3:GetObject\"],\n  \"Resource\": [\"arn:aws:s3:::docs-bucket/*\"]\n},\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\"s3:ListBucket\"],\n  \"Resource\": [\"arn:aws:s3:::docs-bucket\"]\n}","handlingStrategy":"try-catch","validationCode":"import boto3\nsts = boto3.client('sts', aws_access_key_id=k, aws_secret_access_key=s)\nidentity = sts.get_caller_identity()  # cheap auth check\n# then verify ListBucket with an explicit dry probe:\ns3 = boto3.client('s3', aws_access_key_id=k, aws_secret_access_key=s)\ntry:\n    s3.list_objects_v2(Bucket=bucket, MaxKeys=1)\nexcept s3.exceptions.ClientError as e:\n    if e.response['Error']['Code'] in ('AccessDenied', '403'):\n        raise PermissionError('need s3:ListBucket on ' + bucket) from e","typeGuard":null,"tryCatchPattern":"from common.data_source.exceptions import InsufficientPermissionsError\ntry:\n    connector.validate_connector_settings()\nexcept InsufficientPermissionsError as e:\n    alert_ops(f'IAM fix needed: {e}')  # non-retryable config problem\n    raise","preventionTips":["Provision indexing keys with s3:ListBucket on arn:aws:s3:::bucket AND s3:GetObject on arn:aws:s3:::bucket/* from day one","Check bucket policies and SCPs for explicit Deny on ListBucket before shipping a least-privilege key"],"tags":["aws","iam","permissions","s3","validation"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}