{"record":{"id":"b0f795dbf049ca54","repo":"basecamp/kamal","slug":"error-secretid-error-message","errorCode":null,"errorMessage":"#{error['SecretId']}: #{error['Message']}","messagePattern":"#\\{error\\['SecretId'\\]\\}: #\\{error\\['Message'\\]\\}","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/kamal/secrets/adapters/aws_secrets_manager.rb","lineNumber":43,"sourceCode":"          results[\"#{secret_name}\"] = secret[\"SecretString\"]\n        end\n      end\n    end\n\n    def get_from_secrets_manager(secrets, account: nil)\n      args = [ \"aws\", \"secretsmanager\", \"batch-get-secret-value\", \"--secret-id-list\" ] + secrets.map(&:shellescape)\n      args += [ \"--profile\", account.shellescape ] if account\n      args += [ \"--output\", \"json\" ]\n      cmd = args.join(\" \")\n\n      `#{cmd}`.tap do |secrets|\n        raise RuntimeError, \"Could not read #{secrets} from AWS Secrets Manager\" unless $?.success?\n\n        secrets = JSON.parse(secrets)\n\n        return secrets[\"SecretValues\"] unless secrets[\"Errors\"].present?\n\n        raise RuntimeError, secrets[\"Errors\"].map { |error| \"#{error['SecretId']}: #{error['Message']}\" }.join(\" \")\n      end\n    end\n\n    def stringify_secret_value(value)\n      value.is_a?(String) ? value : JSON.dump(value)\n    end\n\n    def check_dependencies!\n      raise RuntimeError, \"AWS CLI is not installed\" unless cli_installed?\n    end\n\n    def cli_installed?\n      `aws --version 2> /dev/null`\n      $?.success?\n    end\nend\n","sourceCodeStart":25,"sourceCodeEnd":60,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/secrets/adapters/aws_secrets_manager.rb#L25-L60","documentation":"batch-get-secret-value can return HTTP 200 while still reporting per-secret failures in its Errors array; this RuntimeError surfaces each one as \"<SecretId>: <Message>\" joined by spaces. The aws CLI exited successfully but AWS refused or could not find at least one requested secret.","triggerScenarios":"ResourceNotFoundException for a secret id that does not exist in the configured account+region; AccessDeniedException when the IAM principal lacks secretsmanager:GetSecretValue/DescribeSecret; InvalidRequestException for malformed ids; secret exists but only in another region or another profile's account.","commonSituations":"Secret name typo or missing environment prefix (e.g. myapp/staging vs myapp/production); IAM policy scoped to specific secret ARNs that omit the requested one; default region pointing at us-east-1 while secrets live in eu-west-1; using the wrong --account profile.","solutions":["Verify the secret exists in the right account/region: aws secretsmanager describe-secret --secret-id <SecretId from the message> --profile <account>","Add the missing IAM permission (secretsmanager:GetSecretValue, secretsmanager:DescribeSecret) for the secret's ARN to the principal in use","Fix the secret name in the kamal secrets fetch arguments to match the full name in AWS","Point at the correct region/profile (export AWS_REGION=... or pass --account matching an aws profile configured for that account)"],"exampleFix":"# error: myapp/RAILS_MASTER_KEY: User is not authorized... \n\n# fix (IAM policy for the CI user/role)\n{\n  \"Effect\": \"Allow\",\n  \"Action\": [\"secretsmanager:GetSecretValue\", \"secretsmanager:DescribeSecret\"],\n  \"Resource\": \"arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/*\"\n}\n\n# then retry\nkamal secrets fetch -a aws_secrets_manager --account prod RAILS_MASTER_KEY","handlingStrategy":"try-catch","validationCode":"# Pre-check each secret exists and is readable before the batch fetch\nsecret_names.each do |name|\n  ok = system(\"aws secretsmanager describe-secret --secret-id #{name.shellescape} #{account ? \"--profile #{account.shellescape}\" : \"\"} > /dev/null 2>&1\")\n  warn \"warning: #{name} not visible to this credentials/region\" unless ok\nend","typeGuard":null,"tryCatchPattern":"begin\n  secrets = adapter.fetch(secret_names, account: account)\nrescue RuntimeError => e\n  # e.message is \"SecretId: AWS message\" pairs — surface them per-secret\n  e.message.split(/ (?=\\S+: )/).each { |pair| warn \"AWS secretsmanager: #{pair}\" }\n  raise\nend","preventionTips":["Scope IAM to secretsmanager:GetSecretValue+DescribeSecret on the exact secret ARN pattern (e.g. arn:...:secret:myapp/*) and test with an deny-simulated policy","Keep the AWS region in deploy scripts and secret naming in sync (myapp/<env>/... convention)","Run the describe-secret pre-check above in CI to catch typos before the deploy needs them"],"tags":["kamal","secrets","aws","iam","permissions","region"],"backgroundTag":"cloud-secret-access-denied","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}