{"record":{"id":"4bbee299f3f8c207","repo":"instructure/canvas-lms","slug":"please-never-delete-the-default-developer-key","errorCode":null,"errorMessage":"Please never delete the default developer key","messagePattern":"Please never delete the default developer key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"app/models/developer_key.rb","lineNumber":199,"sourceCode":"  def ims_registration?\n    ims_registration.present?\n  end\n\n  def validate_redirect_uris\n    uris = redirect_uris&.map do |value|\n      value, _ = CanvasHttp.validate_url(value, allowed_schemes: nil)\n      value\n    end\n\n    errors.add :redirect_uris, \"a redirect_uri is too long\" if uris.any? { |uri| uri.length > 4096 }\n\n    self.redirect_uris = uris unless uris == redirect_uris\n  rescue CanvasHttp::Error, URI::Error, ArgumentError\n    errors.add :redirect_uris, \"is not a valid URI\"\n  end\n\n  def protect_default_key\n    raise \"Please never delete the default developer key\" if workflow_state != \"active\" && self == self.class.default\n  end\n\n  def nullify_empty_icon_url\n    self.icon_url = nil if icon_url.blank?\n  end\n\n  def generate_api_key(overwrite: false)\n    self.api_key = CanvasSlug.generate(nil, 64) if overwrite || !api_key\n  end\n\n  def generate_rsa_keypair!(overwrite: false)\n    return if public_jwk.present? && !overwrite\n\n    key_pair = CanvasSecurity::RSAKeyPair.new\n    @private_jwk = key_pair.to_jwk\n    self.public_jwk = key_pair.public_jwk.to_h\n  end\n","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/developer_key.rb#L181-L217","documentation":"DeveloperKey#protect_default_key is a before_destroy-style validation guard: the site-wide default developer key (used for user-generated access tokens) must always remain in the 'active' workflow state and can never be deleted or deactivated. Raising prevents silent destruction of this critical singleton.","triggerScenarios":"Destroying or setting workflow_state != 'active' on the DeveloperKey for which self == DeveloperKey.default (the special default key seeded per shard/site).","commonSituations":"Cleanup scripts deleting stale/unused developer keys that accidentally include the default key; test factories or seeds wiping keys; console `dk.destroy!` on the default key while deactivating unused integrations.","solutions":["Remove the default key from any destroy/deactivate list: skip records where dk == DeveloperKey.default","Only operate on non-default keys: scope deletions with where.not(id: DeveloperKey.default.id)","If you truly need a new default key, follow Canvas's documented default-key regeneration path, never just delete it","In specs, avoid clearing all DeveloperKeys; exclude the seeded default"],"exampleFix":"// before\nDeveloperKey.where(\"updated_at < ?\", 1.year.ago).destroy_all\n// after\nDeveloperKey.where(\"updated_at < ?\", 1.year.ago).\n  where.not(id: DeveloperKey.default.id).destroy_all","handlingStrategy":"validation","validationCode":"raise 'refusing to delete default key' if key == DeveloperKey.default\nkey.destroy","typeGuard":null,"tryCatchPattern":"begin\n  key.destroy!\nrescue RuntimeError => e\n  Rails.logger.warn(\"skipped default key: #{e.message}\")\nend","preventionTips":["Exclude DeveloperKey.default from any bulk delete/deactivate query","Audit cleanup scripts for unscoped DeveloperKey destruction","Treat the default key as immutable infrastructure"],"tags":["ruby","rails","developer-key","deletion-guard","lti"],"backgroundTag":"permission-denied","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}