{"record":{"id":"53b923a4312c57d6","repo":"infiniflow/ragflow","slug":"provided-blob-storage-credentials-appear-invalid-o","errorCode":null,"errorMessage":"Provided blob storage credentials appear invalid or expired.","messagePattern":"Provided blob storage credentials appear invalid or expired\\.","errorType":"validation","errorClass":"CredentialExpiredError","httpStatus":401,"severity":"error","filePath":"common/data_source/blob_connector.py","lineNumber":335,"sourceCode":"\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\n    # Initialize connector\n    connector = BlobStorageConnector(","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/common/data_source/blob_connector.py#L317-L353","documentation":"Raised by BlobStorageConnector.validate_connector_settings when the probe request fails with error code InvalidAccessKeyId, or SignatureDoesNotMatch with a 401, i.e. the credentials themselves are bad rather than merely under-permissioned. SignatureDoesNotMatch means the secret does not match the access key id; InvalidAccessKeyId means the access key id does not exist (or was rotated/deleted). Wrapped as CredentialExpiredError with a 'invalid or expired' message.","triggerScenarios":"list_objects_v2 probe returning SignatureDoesNotMatch (wrong secret for the key id, or a copy/paste with trailing whitespace), or InvalidAccessKeyId not accompanied by 403/AccessDenied (deleted or rotated access key). Also hit when an OCI/GCS S3-compatible secret key was regenerated but the old one is still configured.","commonSituations":"Access key rotated by an admin and the connector config still holds the old pair; whitespace or newline contamination when pasting secrets; using an S3-compatible provider's console key after deleting it; environment drift between environments (prod secret copied to staging with a different key id).","solutions":["Re-enter the access key id and secret exactly, with no leading/trailing whitespace or newlines","If the key was rotated or deleted, generate a new pair and update the connector credential config","Verify with a direct CLI test: aws s3 ls s3://<bucket> --endpoint-url <ep> using the same credentials","Confirm the key pair belongs to the same account/tenancy as the bucket"],"exampleFix":"# before\ncreds = {\n    'aws_access_key_id': 'AKIA...\\n',   # trailing newline from a file read\n    'aws_secret_access_key': secret,\n}\n# after\ncreds = {\n    'aws_access_key_id': open(key_file).read().strip(),\n    'aws_secret_access_key': open(sec_file).read().strip(),\n}","handlingStrategy":"try-catch","validationCode":"key = credentials['aws_access_key_id'].strip()\nsecret = credentials['aws_secret_access_key'].strip()\nassert key and secret and '\\n' not in secret, 'credential fields must be non-empty and whitespace-clean'\nimport boto3\nboto3.client('s3', aws_access_key_id=key, aws_secret_access_key=secret).list_buckets()  # cheap auth probe","typeGuard":null,"tryCatchPattern":"from common.data_source.exceptions import CredentialExpiredError\ntry:\n    connector.validate_connector_settings()\nexcept CredentialExpiredError as e:\n    if 'invalid or expired' in str(e):\n        prompt_reenter_credentials()  # non-retryable until user acts\n    raise","preventionTips":["Strip secrets when reading from files/env/secret managers — trailing newlines cause SignatureDoesNotMatch","Automate key rotation awareness: store the key id alongside the secret and alert when get_caller_identity stops recognizing it"],"tags":["credentials","s3","authentication","validation"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}