{"record":{"id":"d8ee219871dd0cef","repo":"we-promise/sure","slug":"bad-request-d8ee21","errorCode":"bad_request","errorMessage":"Invalid account number format: #{account_number}","messagePattern":"Invalid account number format: #(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/indexa_capital.rb","lineNumber":106,"sourceCode":"  def get_activities(account_number:, start_date: nil, end_date: nil)\n    Rails.logger.info \"Provider::IndexaCapital - No activities endpoint available for Indexa Capital API\"\n    []\n  end\n\n  private\n\n    RETRYABLE_ERRORS = [\n      SocketError, Net::OpenTimeout, Net::ReadTimeout,\n      Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT, EOFError\n    ].freeze\n\n    MAX_RETRIES = 3\n    INITIAL_RETRY_DELAY = 2 # seconds\n\n    # Indexa Capital account numbers are 8-char alphanumeric (e.g., \"LPYH3MCQ\")\n    def sanitize_account_number!(account_number)\n      unless account_number.present? && account_number.match?(/\\A[A-Za-z0-9]+\\z/)\n        raise Error.new(\"Invalid account number format: #{account_number}\", :bad_request)\n      end\n    end\n\n    attr_reader :username, :document, :password, :api_token\n\n    def validate_configuration!\n      return if @api_token.present?\n\n      if @username.blank? || @document.blank? || @password.blank?\n        raise ConfigurationError, \"Either API token or all three username/document/password credentials are required\"\n      end\n    end\n\n    def token_auth?\n      @api_token.present?\n    end\n\n    def with_retries(operation_name, max_retries: MAX_RETRIES)","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/indexa_capital.rb#L88-L124","documentation":"Raised by sanitize_account_number! before any HTTP call: account_number must be present and match /\\A[A-Za-z0-9]+\\z/. Indexa Capital account numbers are 8-char alphanumeric codes like \"LPYH3MCQ\", and this guard protects the URL path of /accounts/{account_number}/fiscal-results, /portfolio and /performance from injection and malformed paths.","triggerScenarios":"Passing nil/blank; passing an internal UUID or IBAN from another provider's account record; whitespace or a trailing newline in the stored account number; a hyphenated or formatted code copied from a statement PDF.","commonSituations":"Mapping accounts between providers by the wrong identifier, account_number column polluted with display names or whitespace from an import, calling get_holdings with the account 'name' instead of the 'account_number' field returned by list_accounts.","solutions":["Always source the value from list_accounts/extract_accounts output (the account_number key), never from user input or cross-provider IDs","Strip whitespace when storing: account_number.to_s.strip","Pre-validate with the same regex before entering a sync loop so bad records are skipped/logged, not fatal","If a legitimate account number ever contains a dash, loosen the regex deliberately - but confirm with Indexa first"],"exampleFix":"# before\nprovider.get_holdings(account_number: account.external_id) # UUID like \"0f8a...\"\n\n# after\nprovider.get_holdings(account_number: account.account_number.to_s.strip) # \"LPYH3MCQ\"","handlingStrategy":"validation","validationCode":"INDEXA_ACCOUNT_NUMBER = /\\A[A-Za-z0-9]{8}\\z/ # provider requires alphanumeric\nreturn unless (num = account_number.to_s.strip).match?(INDEXA_ACCOUNT_NUMBER)\nprovider.get_holdings(account_number: num)","typeGuard":"def valid_indexa_account_number?(value)\n  value.to_s.strip.match?(/\\A[A-Za-z0-9]{8}\\z/)\nend","tryCatchPattern":"begin\n  provider.get_holdings(account_number: num)\nrescue Provider::IndexaCapital::Error => e\n  raise unless e.error_type == :bad_request && e.message.include?(\"Invalid account number format\")\n  account.update!(sync_disabled: true, disable_reason: \"bad_account_number\")\nend","preventionTips":["Only use account_number values returned by list_accounts - never UUIDs, IBANs or names","Strip whitespace at write time (account_number = value.to_s.strip)","Validate the format before sync loops so one bad record doesn't abort the batch","Never widen the regex without confirming the real format with Indexa"],"tags":["indexa-capital","validation","account-number","input-sanitization","pre-flight"],"backgroundTag":"invalid-identifier-format","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}