instructure/canvas-lms · error

cannot read sis ids

Error message

cannot read sis ids

What it means

UserListV2#initialize parses the address list and dispatches on search_type. When search_type is 'sis_user_id' but the caller lacks SIS read permission (@can_read_sis is false), it raises 'cannot read sis ids' before attempting resolution, to prevent leaking SIS identifiers to unauthorized users.

Solutions

  1. Pass can_read_sis: true only when the acting user truly has :read_sis permission — and then grant the user that permission
  2. Fall back to search_type 'unique_id' when the caller cannot read SIS IDs
  3. Strip SIS IDs from the input list when permissions are absent
  4. Verify the permission object passed to UserListV2 (it should be the enrollment/admin context, not nil)

Example fix

// before
UserListV2.new(params[:user_list], search: :sis_user_id, can_read_sis: false)
// after
search = current_user.can_read_sis?(course) ? :sis_user_id : :unique_id
UserListV2.new(params[:user_list], search: search, can_read_sis: current_user.can_read_sis?(course))
Defensive patterns

Strategy: validation

Validate before calling

raise 'need sis permission' if search_type == :sis_user_id && !acting_user.can_read_sis?(context)

Type guard

def can_resolve_sis?(user, context) = user&.can_read_sis?(context) == true

Try / catch

begin
  UserListV2.new(list, search: :sis_user_id, can_read_sis: can_read)
rescue RuntimeError => e
  raise unless e.message == 'cannot read sis ids'
  UserListV2.new(list, search: :unique_id, can_read_sis: false)
end

Prevention

When it happens

Trigger: Instantiating UserList.new(list, search: :sis_user_id, can_read_sis: false) — e.g. from the course people page, MessageableUser API, or a controller context where the current user lacks :read_sis permission on the account/course — with a list containing SIS IDs.

Common situations: Developers calling UserListV2 in background jobs without passing the permission context; non-admin users entering SIS IDs in the add-people dialog; recently tightened SIS permission checks surfacing in older integrations.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/757753fd278941ff. Report an issue: GitHub.

Appendix: source

Thrown at lib/user_list_v2.rb:58

    @all_results = []
    @resolved_results = []
    @duplicate_results = []
    @missing_results = []

    @root_account = root_account
    @current_user = current_user
    @can_read_sis = can_read_sis
    unless SEARCH_TYPES.include?(search_type)
      raise ParameterError, "search_type must be one of #{SEARCH_TYPES}"
    end

    parse_list(list_in)

    case search_type
    when "unique_id"
      resolve_by_unique_id
    when "sis_user_id"
      raise "cannot read sis ids" unless @can_read_sis

      resolve_by_sis_user_id
    when "cc_path"
      resolve_by_cc_path
    end
    resolve_duplicates_and_missing
  end

  attr_reader :errors, :addresses, :resolved_results, :duplicate_results, :missing_results

  include UserList::Parsing

  def as_json(*)
    {
      users: @resolved_results,
      duplicates: @duplicate_results,
      missing: @missing_results,
      errors: @errors

View on GitHub (pinned to 1c9f0bb801)