{"id":39692,"date":"2026-01-14T07:00:00","date_gmt":"2026-01-14T15:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=39692"},"modified":"2026-01-14T12:43:18","modified_gmt":"2026-01-14T20:43:18","slug":"block-malware-at-upload","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload","title":{"rendered":"How to Block Malware at the Point of Upload"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Allowing users to upload files is the baseline expectation for social apps. Many backend teams do manual checks or trust that users are on their best behavior, but if a harmful file slips through and reaches production, chaos ensues: data loss, system damage, stolen accounts \u2013 all sorts of headaches you shouldn\u2019t have to worry about.<\/p>\n<p>You need a safe checkpoint the moment a file enters your system. Malware scanning at upload stops unsafe files before they spread.<\/p>\n<p>Cloudinary\u2019s Perception Point add-on helps you do this without new servers or complex logic. It scans files for known and unknown threats and returns a clear result. Your app can block unsafe files or hide them until the scan is complete.<\/p>\n<p>This guide shows you how to build that flow with a simple backend and a clean UI that works fast.<\/p>\n<h2>What You\u2019ll Build<\/h2>\n<p>You\u2019ll build a fully automated upload system that checks every file for malware before it becomes usable.<\/p>\n<p>Here\u2019s what the system does:<\/p>\n<ul>\n<li>You upload a file with the Cloudinary Upload Widget.<\/li>\n<li>Cloudinary stores the file in your account.<\/li>\n<li>The Perception Point add-on scans the file for malware.<\/li>\n<li>Your backend listens for scan updates through a webhook.<\/li>\n<li>The frontend polls a status endpoint until the scan is complete.<\/li>\n<li>The UI shows the result, clean or blocked.<\/li>\n<\/ul>\n<p>You don\u2019t need to manage antivirus servers or complex queues. Cloudinary handles the scan and sends a simple status update.<\/p>\n<p>You can test the full project live:<\/p>\n<ul>\n<li>\n<strong>Live Demo<\/strong>: <a href=\"https:\/\/secure-upload-app.vercel.app\/\">https:\/\/secure-upload-app.vercel.app\/<\/a>\n<\/li>\n<li>\n<strong>GitHub Repo<\/strong>: <a href=\"https:\/\/github.com\/musebe\/secure-upload-app\">https:\/\/github.com\/musebe\/secure-upload-app<\/a>\n<\/li>\n<\/ul>\n<h2>How Perception Point Malware Scanning Works<\/h2>\n<p>Cloudinary\u2019s <strong>Perception Point add-on<\/strong> offers a built-in malware scanner that checks every uploaded file, images, videos, PDFs, ZIP files, and many more formats.<\/p>\n<p>When a file is uploaded, Cloudinary creates a <strong>moderation record<\/strong>. The scan moves through three simple states:<\/p>\n<ul>\n<li>\n<strong>pending<\/strong>: The scan is running.<\/li>\n<li>\n<strong>approved<\/strong>: The file is clean.<\/li>\n<li>\n<strong>rejected<\/strong>: The file is unsafe.<\/li>\n<\/ul>\n<p>Cloudinary sends a webhook to your server whenever the scan starts and when it finishes. Each webhook includes the file\u2019s <code>public_id<\/code> and its updated status. Your server can store it, use it, or return it to the UI.<\/p>\n<p>You can read more about the add-on here:<\/p>\n<blockquote>\n<p><a href=\"https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon\">https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon<\/a><\/p>\n<\/blockquote>\n<p>This guide will show you how to receive those updates and display them in your app.<\/p>\n<h2>Project Setup<\/h2>\n<p>Let\u2019s build a small app that lets users upload a file, then watch its malware scan status update in real time. The project has two parts, a static frontend served from the <code>public<\/code> folder, and a simple Express backend hosted on Vercel.<\/p>\n<h3>Clone the Starter Code<\/h3>\n<p>Grab the repo here:<\/p>\n<ul>\n<li>GitHub repo, <a href=\"https:\/\/github.com\/musebe\/secure-upload-app\">https:\/\/github.com\/musebe\/secure-upload-app<\/a>\n<\/li>\n<li>Live demo, <a href=\"https:\/\/secure-upload-app.vercel.app\/\">https:\/\/secure-upload-app.vercel.app\/<\/a>\n<\/li>\n<\/ul>\n<p>Clone it and install the packages:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">git <span class=\"hljs-keyword\">clone<\/span> https:<span class=\"hljs-comment\">\/\/github.com\/musebe\/secure-upload-app<\/span>\ncd secure-upload-app\nnpm install\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Add Your Environment Variables<\/h3>\n<p>Create a <code>.env<\/code> file in the root of the project.<\/p>\n<pre class=\"js-syntax-highlighted\"><code>CLOUDINARY_CLOUD_NAME=your_cloud_name\nCLOUDINARY_API_KEY=your_key\nCLOUDINARY_API_SECRET=your_secret\n<\/code><\/pre>\n<p>These values connect your server to Cloudinary so it can fetch the malware status for each file.<\/p>\n<h3>Start the Project Locally<\/h3>\n<p>Run:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">node<\/span> <span class=\"hljs-selector-tag\">server<\/span><span class=\"hljs-selector-class\">.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Then open:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>http:\/\/localhost:3000\n<\/code><\/pre>\n<p>You should see the upload button and empty \u201cRecent uploads\u201d list.<\/p>\n<h2>Creating the Upload Widget<\/h2>\n<p>The upload widget handles the full upload flow. It sends the file to Cloudinary, triggers the malware scan, and returns the <code>public_id<\/code> that you use to poll for the scan status.<\/p>\n<h3>Add the Widget Script<\/h3>\n<p>Place this script at the top of your <code>index.html<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/upload-widget.cloudinary.com\/latest\/global\/all.js\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This loads the Cloudinary upload widget in the browser.<\/p>\n<h3>Create the Upload Button<\/h3>\n<p>Add this HTML element:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"upload_button\"<\/span>&gt;<\/span>Upload a file<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Initialize the Widget<\/h3>\n<p>Inside <code>app.js<\/code>, set up the widget:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> widget = cloudinary.createUploadWidget(\n  {\n    cloudName: <span class=\"hljs-string\">\"your_cloud_name\"<\/span>,\n    uploadPreset: <span class=\"hljs-string\">\"secure_preset\"<\/span>,\n  },\n  (error, result) =&gt; {\n    <span class=\"hljs-keyword\">if<\/span> (result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n      addUploadToList(result.info.public_id);\n      startStatusPolling(result.info.public_id);\n    }\n  }\n);\n\ndocument\n  .getElementById(<span class=\"hljs-string\">\"upload_button\"<\/span>)\n  .addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, () =&gt; widget.open());\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The widget handles file selection, upload, and returns the upload result. When the upload is done, you save the <code>public_id<\/code> and start polling your backend.<\/p>\n<h3>Confirm Uploads Appear in Your Dashboard<\/h3>\n<p>Go to your Cloudinary Media Library. You should see each uploaded file inside the folder you set in the preset.<\/p>\n<h2>Turn On Malware Scanning With Perception Point<\/h2>\n<p>Right now, the widget uploads files to Cloudinary. To block malware at upload time, you need one extra step. Tell Cloudinary to send every file through the <strong>Perception Point<\/strong> malware scanner.<\/p>\n<p>You do this with an <a href=\"https:\/\/cloudinary.com\/documentation\/upload_presets\"><strong>upload preset<\/strong>.<\/a><\/p>\n<h3>Enable the Add-on in Cloudinary<\/h3>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/demo-article-projects\/image\/upload\/v1763512975\/secure_malware_scan\/Screenshot_2025-11-19_at_01.49.26.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1912\" height=\"900\"\/><\/p>\n<p>In your Cloudinary account:<\/p>\n<ol>\n<li>Go to <strong>Settings<\/strong> &gt; <strong>Add-ons<\/strong>.<\/li>\n<li>Find <strong>Perception Point Malware Detection<\/strong>.<\/li>\n<li>Activate it for your account.<\/li>\n<\/ol>\n<blockquote>\n<p>You can read the full docs here: <a href=\"https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon\">https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon<\/a><\/p>\n<\/blockquote>\n<h3>Create an Upload Preset for Secure Uploads<\/h3>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/demo-article-projects\/image\/upload\/v1763512974\/secure_malware_scan\/Screenshot_2025-11-19_at_01.36.46.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1916\" height=\"892\"\/><\/p>\n<p>In <strong>Settings<\/strong> &gt; <strong>Upload<\/strong> &gt; <strong>Upload presets<\/strong>:<\/p>\n<ol>\n<li>\n<p>Click <strong>Add upload preset<\/strong>.<\/p>\n<\/li>\n<li>\n<p>Set <strong>Name<\/strong> to something clear, for example:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>secure_malware_scan\n<\/code><\/pre>\n<\/li>\n<li>\n<p>Under <strong>Upload control<\/strong>, set:<\/p>\n<ul>\n<li>\n<strong>Unsigned<\/strong> to <strong>Enabled<\/strong> (for the widget).<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Under <strong>Moderation<\/strong>:<\/p>\n<ul>\n<li>Choose <strong>Perception Point<\/strong>.<\/li>\n<li>Make sure it is set to <strong>Auto<\/strong> so each upload triggers a scan.<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Optional but nice:<\/p>\n<ul>\n<li>Set <strong>Folder<\/strong> to <code>secure_malware_scan<\/code> so all files live in one place.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Save the preset.<\/p>\n<p>Now every upload that uses this preset will be scanned.<\/p>\n<h3>Point the Widget at the Secure Preset<\/h3>\n<p>In your <code>app.js<\/code>, set the same preset name:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> uploadWidget = cloudinary.createUploadWidget(\n  {\n    <span class=\"hljs-attr\">cloudName<\/span>: <span class=\"hljs-string\">\"your_cloud_name\"<\/span>,\n    <span class=\"hljs-attr\">uploadPreset<\/span>: <span class=\"hljs-string\">\"secure_malware_scan\"<\/span>,\n    <span class=\"hljs-attr\">folder<\/span>: <span class=\"hljs-string\">\"secure_malware_scan\"<\/span>,\n  },\n  onUploadComplete\n);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You can see this wired up in the repo here:<\/p>\n<ul>\n<li>\n<code>public\/app.js<\/code> in <a href=\"https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/public\/app.js\">https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/public\/app.js<\/a>\n<\/li>\n<\/ul>\n<p>From this point:<\/p>\n<ul>\n<li>Every upload goes into <code>secure_malware_scan<\/code>.<\/li>\n<li>Each file gets a <code>moderation<\/code> record.<\/li>\n<li>The first status is <code>pending<\/code>.<\/li>\n<li>Perception Point then decides <code>approved<\/code> or <code>rejected<\/code>.<\/li>\n<\/ul>\n<h2>Building the Express Backend<\/h2>\n<p>The backend sits between Cloudinary and your UI and gives you safe, clean status data for each file.<\/p>\n<p>You can view the full server code here:<\/p>\n<ul>\n<li>\n<p><code>server.js<\/code><\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/server.js\">https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/server.js<\/a><\/p>\n<\/li>\n<\/ul>\n<p>The backend does three things:<\/p>\n<ol>\n<li>Exposes simple health and debug routes.<\/li>\n<li>Fetches malware status for a file from Cloudinary.<\/li>\n<li>Receives webhooks from Cloudinary when scans complete.<\/li>\n<\/ol>\n<h3>Basic Express Setup<\/h3>\n<p>You use Express, CORS, dotenv, and the Cloudinary SDK.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> express = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">\"express\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> cors = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">\"cors\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> dotenv = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">\"dotenv\"<\/span>);\n<span class=\"hljs-keyword\">const<\/span> { v2: cloudinary } = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">\"cloudinary\"<\/span>);\n\ndotenv.config();\n\n<span class=\"hljs-keyword\">const<\/span> app = express();\n\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">express<\/span>.<span class=\"hljs-title\">json<\/span>());\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">cors<\/span>());\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Cloudinary is configured with the same values you set in your <code>.env<\/code>:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">cloudinary<\/span><span class=\"hljs-selector-class\">.config<\/span>({\n  <span class=\"hljs-attribute\">cloud_name<\/span>: process.env.CLOUDINARY_CLOUD_NAME,\n  api_key: process.env.CLOUDINARY_API_KEY,\n  api_secret: process.env.CLOUDINARY_API_SECRET,\n});\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Status Lookup Route<\/h3>\n<p>The backend pulls moderation info from Cloudinary using the asset\u2019s <code>public_id<\/code>. Your UI calls this route while the scan is running.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app.get(<span class=\"hljs-string\">\"\/api\/status\/:publicId\"<\/span>, <span class=\"hljs-keyword\">async<\/span> (req, res) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> publicId = req.params.publicId;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> resource = <span class=\"hljs-keyword\">await<\/span> cloudinary.api.resource(publicId, {\n      <span class=\"hljs-attr\">resource_type<\/span>: <span class=\"hljs-string\">\"image\"<\/span>,\n    });\n\n    <span class=\"hljs-keyword\">const<\/span> moderationArr = resource.moderation || &#91;];\n    <span class=\"hljs-keyword\">const<\/span> moderation = moderationArr.find(<span class=\"hljs-function\">(<span class=\"hljs-params\">m<\/span>) =&gt;<\/span> m.kind === <span class=\"hljs-string\">\"perception_point\"<\/span>);\n\n    <span class=\"hljs-keyword\">const<\/span> status = moderation ? moderation.status : <span class=\"hljs-string\">\"none\"<\/span>;\n\n    res.json({\n      status,\n      <span class=\"hljs-attr\">kind<\/span>: moderation ? moderation.kind : <span class=\"hljs-literal\">null<\/span>,\n    });\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    res.status(<span class=\"hljs-number\">500<\/span>).json({ <span class=\"hljs-attr\">error<\/span>: <span class=\"hljs-string\">\"Failed to get status\"<\/span> });\n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The frontend does not need to know any Cloudinary details. It only cares about <code>status<\/code>, which will be <code>pending<\/code>, <code>approved<\/code>, or <code>rejected<\/code>.<\/p>\n<h2>Handling the Cloudinary Webhook<\/h2>\n<p>Polling works, but webhooks give you the full story.<\/p>\n<p>Cloudinary sends you two key events:<\/p>\n<ul>\n<li>When the file is uploaded.<\/li>\n<li>When the malware scan is done.<\/li>\n<\/ul>\n<p>You receive both at the <code>\/api\/cloudinary-webhook<\/code> endpoint.<\/p>\n<p>You can see the full handler here:<\/p>\n<ul>\n<li>\n<p><code>server.js<\/code><\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/server.js\">https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/server.js<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>Configure the Webhook in Cloudinary<\/h3>\n<p>In your Cloudinary dashboard:<\/p>\n<ol>\n<li>\n<p>Go to <strong>Settings \u2192 Webhooks<\/strong>.<\/p>\n<\/li>\n<li>\n<p>Add this URL in production:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>https:\/\/secure-upload-app.vercel.app\/api\/cloudinary-webhook\n<\/code><\/pre>\n<p>In local dev you used an ngrok URL like:<\/p>\n<pre class=\"js-syntax-highlighted\"><code>https:\/\/your-id.ngrok-free.app\/api\/cloudinary-webhook\n<\/code><\/pre>\n<\/li>\n<\/ol>\n<h3>Choose All Notifications or at Least Moderation<\/h3>\n<p>Cloudinary now sends JSON payloads to your server after each upload and after each scan.<\/p>\n<blockquote>\n<p>Docs for the payload shape are here: <a href=\"https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon\">https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon<\/a><\/p>\n<\/blockquote>\n<h3>Add the Webhook Route<\/h3>\n<p>The handler in this project keeps things simple.<\/p>\n<p>It just logs what came in and returns <code>200<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app.post(<span class=\"hljs-string\">\"\/api\/cloudinary-webhook\"<\/span>, (req, res) =&gt; {\n  <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Incoming webhook:\"<\/span>, req.body);\n  res.status(<span class=\"hljs-number\">200<\/span>).json({ <span class=\"hljs-attr\">received<\/span>: <span class=\"hljs-literal\">true<\/span> });\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You\u2019ll see two logs per file:<\/p>\n<ul>\n<li>One with <code>notification_type: &quot;upload&quot;<\/code> and <code>status: &quot;pending&quot;<\/code>.<\/li>\n<li>One with <code>notification_type: &quot;moderation&quot;<\/code> and <code>moderation_status: &quot;approved&quot;<\/code> or <code>&quot;rejected&quot;<\/code>.<\/li>\n<\/ul>\n<p>You can later:<\/p>\n<ul>\n<li>Store these in a database.<\/li>\n<li>Trigger alerts when a file is rejected.<\/li>\n<li>Sync other systems.<\/li>\n<\/ul>\n<p>For this demo, polling is enough for the UI. The webhook is there to show how the scan behaves behind the scenes.<\/p>\n<h2>Polling Malware Status From the Frontend<\/h2>\n<p>Once a file is uploaded, you know its <code>public_id<\/code>. Now the frontend needs to ask your backend, \u201cIs this file safe yet?\u201d That\u2019s where <code>\/api\/status\/:publicId<\/code> comes in.<\/p>\n<p>You can see the full UI logic here:<\/p>\n<ul>\n<li>\n<p><code>public\/app.js<\/code><\/p>\n<p><a href=\"https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/public\/app.js\">https:\/\/github.com\/musebe\/secure-upload-app\/blob\/main\/public\/app.js<\/a><\/p>\n<\/li>\n<\/ul>\n<p>And the live result here:<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/secure-upload-app.vercel.app\/\">https:\/\/secure-upload-app.vercel.app<\/a>\n<\/li>\n<\/ul>\n<h3>Build the Upload Card<\/h3>\n<p>Each upload appears as a \u201ccard\u201d in the <strong>Recent uploads<\/strong> section.<\/p>\n<p>You\u2019ll be able to track file name and size, Cloudinary <code>public_id<\/code>, and the current malware status.<\/p>\n<p>A simplified version of the DOM creation:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildUploadItem<\/span>(<span class=\"hljs-params\">info, status<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> item = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">\"div\"<\/span>);\n  item.className = <span class=\"hljs-string\">\"upload-item\"<\/span>;\n  item.dataset.publicId = info.public_id;\n\n  <span class=\"hljs-keyword\">const<\/span> chip = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">\"span\"<\/span>);\n  chip.className = <span class=\"hljs-string\">\"status-chip\"<\/span>;\n\n  item.appendChild(chip);\n  updateStatusChip(item, status, info);\n\n  <span class=\"hljs-keyword\">return<\/span> item;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The <code>status-chip<\/code> content changes based on the scan state.<\/p>\n<h3>Show Pending, Safe, or Blocked<\/h3>\n<p>The status chip updates its text and class based on the status value:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateStatusChip<\/span>(<span class=\"hljs-params\">item, status, info<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> chip = item.querySelector(<span class=\"hljs-string\">\".status-chip\"<\/span>);\n\n  <span class=\"hljs-keyword\">if<\/span> (status === <span class=\"hljs-string\">\"approved\"<\/span>) {\n    chip.textContent = <span class=\"hljs-string\">\"Safe\"<\/span>;\n    chip.classList.add(<span class=\"hljs-string\">\"status-safe\"<\/span>);\n  } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (status === <span class=\"hljs-string\">\"rejected\"<\/span>) {\n    chip.textContent = <span class=\"hljs-string\">\"Blocked\"<\/span>;\n    chip.classList.add(<span class=\"hljs-string\">\"status-blocked\"<\/span>);\n  } <span class=\"hljs-keyword\">else<\/span> {\n    chip.textContent = <span class=\"hljs-string\">\"Pending scan\"<\/span>;\n    chip.classList.add(<span class=\"hljs-string\">\"status-pending\"<\/span>);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Your CSS handles the colors for <code>.status-safe<\/code>, <code>.status-pending<\/code>, and <code>.status-blocked<\/code>.<\/p>\n<h3>Poll the Backend Until the Scan Finishes<\/h3>\n<p>When Cloudinary reports <code>event: &quot;success&quot;<\/code> for an upload, you start polling <code>\/api\/status\/:publicId<\/code> until the scan leaves the <code>pending<\/code> state.<\/p>\n<p>Core polling logic:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">startStatusPolling<\/span>(<span class=\"hljs-params\">info, item<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> publicId = info.public_id;\n\n  <span class=\"hljs-keyword\">const<\/span> poll = <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">`\/api\/status\/<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">encodeURIComponent<\/span>(publicId)}<\/span>`<\/span>);\n\n    <span class=\"hljs-keyword\">if<\/span> (!res.ok) {\n      setTimeout(poll, <span class=\"hljs-number\">4000<\/span>);\n      <span class=\"hljs-keyword\">return<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> res.json();\n\n    <span class=\"hljs-keyword\">if<\/span> (!data.status || data.status === <span class=\"hljs-string\">\"pending\"<\/span>) {\n      setTimeout(poll, <span class=\"hljs-number\">4000<\/span>);\n      <span class=\"hljs-keyword\">return<\/span>;\n    }\n\n    updateStatusChip(item, data.status, info);\n  };\n\n  poll();\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The important part is simple. While <code>status<\/code> is <code>pending<\/code>, keep retrying. Once you get <code>approved<\/code> or <code>rejected<\/code>, update the card and stop. This gives users clear feedback without a reload.<\/p>\n<h2>The Upload Button and How It Works<\/h2>\n<p>The upload flow starts with one element, the <strong>Upload a file<\/strong> button. This button triggers Cloudinary\u2019s Upload Widget and hands every file to your preset, which includes the malware scanning add-on.<\/p>\n<h3>The Button in Your HTML<\/h3>\n<p>In <code>public\/index.html<\/code>, all you need is:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"upload_button\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"btn-primary\"<\/span>&gt;<\/span>\n  Upload a file\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is the only element you interact with in JavaScript. Everything else is handled by the widget.<\/p>\n<h3>Connect the Button in <code>app.js<\/code><\/h3>\n<p>The widget is launched when the user clicks this button:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> uploadButton = document.getElementById(<span class=\"hljs-string\">\"upload_button\"<\/span>);\n\n<span class=\"hljs-keyword\">const<\/span> widget = cloudinary.createUploadWidget(\n  {\n    cloudName: CLOUD_NAME,\n    uploadPreset: <span class=\"hljs-string\">\"secure_malware_scan\"<\/span>,\n    sources: &#91;<span class=\"hljs-string\">\"local\"<\/span>, <span class=\"hljs-string\">\"camera\"<\/span>, <span class=\"hljs-string\">\"url\"<\/span>],\n  },\n  (error, result) =&gt; {\n    <span class=\"hljs-keyword\">if<\/span> (result.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n      handleUpload(result.info);\n    }\n  }\n);\n\nuploadButton.addEventListener(<span class=\"hljs-string\">\"click\"<\/span>, () =&gt; {\n  widget.open();\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This gives the user three options:<\/p>\n<ul>\n<li>Upload from their device<\/li>\n<li>Upload from camera<\/li>\n<li>Upload from a URL<\/li>\n<\/ul>\n<p>The widget handles file selection, previews, and upload progress.<\/p>\n<h3>What Happens after the User Uploads?<\/h3>\n<p>When the upload finishes:<\/p>\n<ul>\n<li>Cloudinary stores the asset.<\/li>\n<li>The Perception Point add-on starts scanning.<\/li>\n<li>The widget returns the full upload info to your <code>handleUpload<\/code> function.<\/li>\n<li>You render the file to the page in a \u201cpending scan\u201d state.<\/li>\n<li>Your polling loop starts checking <code>\/api\/status\/:publicId<\/code>.<\/li>\n<\/ul>\n<p>This is the start of the flow that updates the UI every few seconds until Cloudinary marks the file as <strong>approved<\/strong> or <strong>rejected<\/strong>.<\/p>\n<h2>Conclusion<\/h2>\n<p>You now have a working system that blocks malware at the point of upload. Cloudinary handles the file, the Perception Point add-on scans it, and your backend polls for updates until the file is marked safe or blocked. The frontend stays simple, the status updates in real time, and you get a full, secure workflow without building a scanner yourself.<\/p>\n<p>This approach scales well. You can drop the upload widget into any app, attach the same preset, and immediately add malware protection. If you want to extend the setup, you can add queued processing, user notifications, or database logging. The core idea stays the same: scan everything at the moment it enters your system.<\/p>\n<p>You can explore the full code here:<\/p>\n<ul>\n<li>GitHub repo: <a href=\"https:\/\/github.com\/musebe\/secure-upload-app\">**https:\/\/github.com\/musebe\/secure-upload-app**<\/a>\n<\/li>\n<li>Live demo: <a href=\"https:\/\/secure-upload-app.vercel.app\/\">**https:\/\/secure-upload-app.vercel.app\/**<\/a>\n<\/li>\n<li>Cloudinary add-on docs: <a href=\"https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon\">**https:\/\/cloudinary.com\/documentation\/perception_point_malware_detection_addon**<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":39696,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[300],"class_list":["post-39692","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-user-generated-content"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>How to Block Malware at the Point of Upload<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Block Malware at the Point of Upload\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-01-14T15:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-01-14T20:43:18+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"How to Block Malware at the Point of Upload\",\"datePublished\":\"2026-01-14T15:00:00+00:00\",\"dateModified\":\"2026-01-14T20:43:18+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload\"},\"wordCount\":9,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA\",\"keywords\":[\"User-Generated Content\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2026\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload\",\"url\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload\",\"name\":\"How to Block Malware at the Point of Upload\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA\",\"datePublished\":\"2026-01-14T15:00:00+00:00\",\"dateModified\":\"2026-01-14T20:43:18+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Block Malware at the Point of Upload\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"How to Block Malware at the Point of Upload","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload","og_locale":"en_US","og_type":"article","og_title":"How to Block Malware at the Point of Upload","og_url":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload","og_site_name":"Cloudinary Blog","article_published_time":"2026-01-14T15:00:00+00:00","article_modified_time":"2026-01-14T20:43:18+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"How to Block Malware at the Point of Upload","datePublished":"2026-01-14T15:00:00+00:00","dateModified":"2026-01-14T20:43:18+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload"},"wordCount":9,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA","keywords":["User-Generated Content"],"inLanguage":"en-US","copyrightYear":"2026","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload","url":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload","name":"How to Block Malware at the Point of Upload","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA","datePublished":"2026-01-14T15:00:00+00:00","dateModified":"2026-01-14T20:43:18+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/block-malware-at-upload"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/block-malware-at-upload#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to Block Malware at the Point of Upload"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1768258055\/Blog_How_to_Block_Malware_at_the_Point_of_Upload\/Blog_How_to_Block_Malware_at_the_Point_of_Upload.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39692","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=39692"}],"version-history":[{"count":5,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39692\/revisions"}],"predecessor-version":[{"id":39700,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39692\/revisions\/39700"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/39696"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=39692"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=39692"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=39692"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}