{"record":{"id":"c8b917ef70539cef","repo":"docusealco/docuseal","slug":"file-type-extension-is-not-allowed-c8b917","errorCode":null,"errorMessage":"File type '.#{extension}' is not allowed.","messagePattern":"File type '\\.#(.+?)' is not allowed\\.","errorType":"validation","errorClass":"Submitters::NormalizeValues::InvalidDefaultValue","httpStatus":422,"severity":"error","filePath":"lib/submitters/normalize_values.rb","lineNumber":282,"sourceCode":"\n      checksum = Digest::MD5.base64digest(data)\n\n      blob = find_blob_by_checksum(checksum, account)\n\n      blob || ActiveStorage::Blob.create_and_upload!(\n        io: StringIO.new(data),\n        filename: \"#{type}.png\",\n        content_type: 'image/png',\n        metadata: { analyzed: true, identified: true, width:, height: }\n      )\n    end\n\n    def find_or_create_blob_from_url(account, url)\n      filename = Addressable::URI.parse(url).path.split('/').last.to_s\n      extension = File.extname(filename).delete_prefix('.').downcase\n\n      if Submitters::DANGEROUS_EXTENSIONS.include?(extension)\n        raise InvalidDefaultValue, \"File type '.#{extension}' is not allowed.\"\n      end\n\n      cache_key = [account.id, url].join(':')\n      checksum = CHECKSUM_CACHE_STORE.fetch(cache_key)\n\n      blob = find_blob_by_checksum(checksum, account) if checksum\n\n      return blob if blob\n\n      data = DownloadUtils.call(url, validate: true).body\n\n      checksum = Digest::MD5.base64digest(data)\n\n      CHECKSUM_CACHE_STORE.write(cache_key, checksum)\n\n      blob = find_blob_by_checksum(checksum, account)\n\n      blob || ActiveStorage::Blob.create_and_upload!(io: StringIO.new(data), filename:)","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/docusealco/docuseal/blob/004a22c1c88109c7ba0b567df011a8cb13894001/lib/submitters/normalize_values.rb#L264-L300","documentation":"Submitters::NormalizeValues#find_or_create_blob_from_url (lib/submitters/normalize_values.rb:282) raises InvalidDefaultValue when the extension of the URL's last path segment (File.extname, downcased) is in Submitters::DANGEROUS_EXTENSIONS. The check runs before any download: it is purely filename-based on the remote URL, so 'https://host/payload.exe' is rejected immediately while extension-less URLs proceed to DownloadUtils with SSRF validation.","triggerScenarios":"An API default_value URL ending in .exe/.sh/.jar/.dll/.apk etc.; URLs with query strings after the dangerous extension (extname still resolves to the last segment); test/probing calls pointing at executable downloads.","commonSituations":"Integrations pre-filling attachments from asset servers that host both documents and binaries; URL signed-asset filenames retaining the original dangerous extension; users pasting direct download links to installers.","solutions":["Point the URL at a safe, allowed document/image resource (pdf, png, jpg, docx...).","If the asset is legitimately named with a dangerous extension, re-host it under a safe filename or bundle it into a zip/pdf and link that.","Rescue Submitters::NormalizeValues::InvalidDefaultValue and surface the message as a 422 so callers see the rejected extension."],"exampleFix":"# before\n{ 'default_value' => 'https://cdn.example.com/downloads/setup.exe' }\n\n# after\n{ 'default_value' => 'https://cdn.example.com/downloads/instructions.pdf' }","handlingStrategy":"validation","validationCode":"# Check the URL's last path segment against the same blocklist\next = File.extname(URI.parse(url).path.split('/').last.to_s).delete_prefix('.').downcase\nraise ArgumentError, \"File type '.#{ext}' is not allowed.\" if Submitters::DANGEROUS_EXTENSIONS.include?(ext)","typeGuard":null,"tryCatchPattern":"begin\n  Submitters::NormalizeValues.normalize_attachment_value(url, field, account, attachments, purpose: :api)\nrescue Submitters::NormalizeValues::InvalidDefaultValue => e\n  render json: { error: e.message }, status: :unprocessable_entity\nend","preventionTips":["Link to safe document/image assets only; keep dangerous binaries off asset URLs.","The check reads the URL path, not Content-Type — re-host under a safe filename when needed.","Signed-URL filenames inherit the original extension; generate them from safe originals."],"tags":["docuseal","default-value","url-validation","security","extension-blocklist"],"backgroundTag":"blocked-file-extension","analyzedSha":"004a22c1c88109c7ba0b567df011a8cb13894001","analyzedAt":"2026-08-21T13:38:23.343Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}