MetaDefender Anti-Malware Protection

Cloudinary is a cloud-based service that provides an end-to-end image and video management solution including uploads, storage, manipulations, optimizations and delivery.

Cloudinary offers a very rich set of asset uploading, manipulation and digital asset management capabilities. Cloudinary allows you to upload media files to the cloud, manipulate them on-the-fly and deliver them to your users optimized and cached via a fast CDN.

OPSWAT's MetaDefender detects and prevents advanced threats by incorporating multi-scanning and controlled data workflows. Cloudinary provides an add-on that enables you to incorporate MetaDefender's anti-malware detection into your media management activities. The MetaDefender add-on leverages multiple antivirus engines simultaneously to prevent viruses and malware from infecting your website or mobile application.

With MetaDefender's anti-malware add-on, you can extend Cloudinary's powerful cloud-based media library and delivery capabilities by automatically scanning both your own assets and those uploaded by your users. When using the MetaDefender add-on, uploaded files are automatically scanned, making sure that no virus or malicious code is delivered to your web and mobile viewers.

Automatic file scanning flow

The following describes the basic flow of uploading and displaying moderated assets using Cloudinary and the MetaDefender anti-malware add-on:

  • File upload

    • The asset is uploaded to Cloudinary.
    • The uploaded assets are set to a 'pending' status, with short-term CDN caching.
  • Asset moderation

    • The uploaded asset is sent to MetaDefender for asynchronous moderation in the background.
    • The asset is either approved or rejected by MetaDefender's anti-malware add-on.
    • An optional notification callback is sent to your application with the virus scanning results.
    • If the asset is approved, its cache settings are modified to be long-term.
    • A rejected asset is removed and moved to a secondary backup repository.
  • Manual override

    • Pending, approved and rejected assets can be listed programatically using Cloudinary's API or interactively using our online Media Library Web interface.
    • You can manually override the automatic moderation using the API or Media Library.

Request file scanning

To request moderation of uploaded assets, set the moderation upload parameter to metascan:

Ruby:
Cloudinary::Uploader.upload("local_file.jpg", 
  :moderation => "metascan")
PHP:
\Cloudinary\Uploader::upload("local_file.jpg", 
  array("moderation" => "metascan"));
Python:
cloudinary.uploader.upload("local_file.jpg",
  moderation = "metascan")
Node.js:
cloudinary.v2.uploader.upload("local_file.jpg", 
  { moderation: "metascan" }),
  function(error, result){console.log(result);});
Java:
cloudinary.uploader().upload("local_file.jpg", 
  ObjectUtils.asMap("moderation", "metascan"));
.Net:
var uploadParams = new ImageUploadParams() 
{
  File = new FileDescription(@"local_file.jpg"),
  Moderation = "metascan"
};
var uploadResult = cloudinary.Upload(uploadParams);

The same goes for videos or raw files. Just make sure to set the resource_type parameter to video or raw when calling the upload method. For example:

Ruby:
Cloudinary::Uploader.upload("virus_check.txt", 
  :resource_type => :raw,
  :moderation => "metascan")
PHP:
\Cloudinary\Uploader::upload("virus_check.txt", 
  array(
    "resource_type" => "raw", 
    "moderation" => "metascan"));
Python:
cloudinary.uploader.upload("virus_check.txt", 
  resource_type = "raw",
  moderation = "metascan")
Node.js:
cloudinary.v2.uploader.upload("virus_check.txt", 
  { moderation: "metascan", 
    resource_type: "raw" }),
  function(error, result){console.log(result);});
Java:
cloudinary.uploader().upload("virus_check.txt",  
  ObjectUtils.asMap(
    "resource_type", "raw", 
    "moderation", "metascan"));
.Net:
var uploadParams = new RawUploadParams() 
{
  File = new FileDescription(@"virus_check.txt"),
  Moderation = "metascan"
};
var uploadResult = cloudinary.Upload(uploadParams);

the MetaDefender malware add-on performs the asset moderation asynchronously and should be completed within a few seconds or minutes, depending on file size.

The following snippet shows a sample response of an upload API call that indicates that the MetaDefender moderation is in the pending status.

{
  "public_id":"qtxkfkpmpiivr15bcrjh",
  "version":1531918030,
    ...
    ... 
 "secure_url":
  "https://res.cloudinary.com/demo/image/upload/v1531918030/qtxkfkpmpiivr15bcrjh.jpg",
 "moderation":[{"status":"pending", "kind":"metascan"}],
   ...
}

Status notification

Due to the fact that the MetaDefender add-on moderates media assets asynchronously, you may want to request a notification when the scanning process is completed.

When calling the upload API with the metascan parameter, you can set the notification_url to a public HTTP or HTTPS notification URL. Cloudinary sends a POST request to the specified endpoint when that MetaDefender scanning finishes.

Ruby:
Cloudinary::Uploader.upload("local_file.jpg", 
  :moderation => "metascan",
  :notification_url => "http://mysite.example.com/hooks",
  :public_id => "local_file")
PHP:
\Cloudinary\Uploader::upload("local_file.jpg", 
  array(
    "moderation" => "metascan",
    "notification_url" => "http://mysite.example.com/hooks",
    "public_id" => "local_file"));
Python:
cloudinary.uploader.upload("local_file.jpg",
  moderation = "metascan",
  notification_url = "http://mysite.example.com/hooks",
  public_id = "local_file")
Node.js:
cloudinary.v2.uploader.upload("local_file.jpg", 
  { moderation: "metascan",
    notification_url: "http://mysite.example.com/hooks",
    public_id: "local_file" },
  function(error, result){console.log(result);});
Java:
cloudinary.uploader().upload("local_file.jpg",
  ObjectUtils.asMap(
    "moderation", "metascan",
    "notification_url", "http://mysite.example.com/hooks",
    "public_id", "local_file"));
.Net:
var uploadParams = new ImageUploadParams() 
{
  File = new FileDescription(@"local_file.jpg"),
  Moderation = "metascan",
  PublicId = "local_file",
  NotificationUrl = "http://mysite.example.com/hooks"
};
var uploadResult = cloudinary.Upload(uploadParams);

The following JSON snippets are examples of POST requests sent to the notification URL when moderation is completed. The moderation_status value can be either approved or rejected:

{
  { 
    "moderation_response": 
    {
      "result_code": 0,
      "result": "No threat detected",
      "engine_results": [
        {
          "engine": "AegisLab",
          "result_code": 0,
          "threat_found": ""
        },
        {
          "engine": "Ahnlab",
          "result_code": 0,
          "threat_found": ""
        },
        {
          ...
            additional engines here
          ...
        }  
      ]
    },
    "moderation_status": "approved",
    "moderation_kind": "metascan",
    "moderation_updated_at": "2018-07-18T13:13:19Z",
      ...
      ...
    "notification_type": "moderation"
  }
}
 {
  "moderation_response": {
    "result_code": 1,
    "result": "Infected",
    "engine_results": [
      {
        "engine": "AegisLab",
        "result_code": 1,
        "threat_found": "EICAR-AV-Test"
      },
      {
        "engine": "Ahnlab",
        "result_code": 1,
        "threat_found": "EICAR_Test_File"
      },
      {
        ...
          additional engines here
        ...
      }    
    ]
  },
  "moderation_status": "rejected",
  "moderation_kind": "metascan",
  "moderation_updated_at": "2018-07-18T14:06:58Z",
    ...
    ...
  "notification_type": "moderation"
}

Query status

As an alternative to receiving a webhook when the moderation check finishes, you can poll Cloudinary's Admin API to check the moderation status of a previously uploaded asset:

Ruby:
Cloudinary::Api.resource('l8yvniicjhmyxuuqqyqy')
PHP:
$api = new \Cloudinary\Api();
$api->resource("l8yvniicjhmyxuuqqyqy");
Python:
cloudinary.api.resource("l8yvniicjhmyxuuqqyqy")
Node.js:
cloudinary.v2.api.resource('l8yvniicjhmyxuuqqyqy', 
  function(error, result){console.log(result);});
Java:
cloudinary.api().resource("l8yvniicjhmyxuuqqyqy", 
  ObjectUtils.emptyMap());
.Net:
var getResourceResult = cloudinary.GetResource(new GetResourceParams("l8yvniicjhmyxuuqqyqy"));

The following JSON snippet is the response of the Admin API resource details of a moderated image. As you can see, the moderation status is set to approved.

{"public_id":"blue_suit",
 "format":"jpg",
 "version":1531919587,
 "resource_type":"image",
 "type":"upload",
 "created_at":"2018-07-18T13:13:07Z",
 "bytes":21218,
 "width":361,
 "height":452,
 "url":
  "http://res-staging.cloudinary.com/newml/image/upload/v1531919587/blue_suit.jpg",
 "secure_url":
  "https://res-staging.cloudinary.com/newml/image/upload/v1531919587/blue_suit.jpg",
 "moderation":
  [{"response":
     {"result_code":0,
      "result":"No threat detected",
      "engine_results":
       [{"engine":"AegisLab", "result_code":0, "threat_found":""},
        {"engine":"Ahnlab", "result_code":0, "threat_found":""},
        {"engine":"ByteHero", "result_code":0, "threat_found":""},
        {"engine":"ClamAV", "result_code":0, "threat_found":""},
        {"engine":"Cyren", "result_code":0, "threat_found":""},
        {"engine":"Emsisoft", "result_code":0, "threat_found":""},
        {"engine":"Filseclab", "result_code":0, "threat_found":""},
        {"engine":"K7", "result_code":0, "threat_found":""},
        {"engine":"NANOAV", "result_code":0, "threat_found":""},
        {"engine":"Quick Heal", "result_code":0, "threat_found":""},
        {"engine":"Vir.IT eXplorer", "result_code":0, "threat_found":""},
        {"engine":"Vir.IT ML", "result_code":0, "threat_found":""},
        {"engine":"Xvirus Personal Guard",
         "result_code":0,
         "threat_found":""},
        {"engine":"Zillya!", "result_code":0, "threat_found":""}]},
    "status":"approved",
    "kind":"metascan",
    "updated_at":"2018-07-18T13:13:19Z"}],
 "next_cursor":
  "2c9c91253385d01bf4e2293b64a9dc9c4aa5904b4e78a0bb107b99ae0a851dd7",
 "derived":[]}

The example below queries the MetaDefender moderation status of an uploaded raw text file:

Ruby:
Cloudinary::Api.resource('virus-test.txt', :resource_type : "raw")
PHP:
$api = new \Cloudinary\Api();
$api->resource("virus-test.txt", array("resource_type" => "raw"));
Python:
cloudinary.api.resource("virus-test.txt", resource_type = "raw")
Node.js:
cloudinary.v2.api.resource('virus-test.txt', 
  { resource_type: "raw" },
  function(error, result){console.log(result);});
Java:
cloudinary.api().resource("virus-test.txt", 
  ObjectUtils.asMap("resource_type", "raw"));
.Net:
var getResourceParams = new GetResourceParams("virus-test.txt")
{
  ResourceType = "raw"
}
var getResourceResult = cloudinary.GetResource(getResourceParams);

This file contains a virus signature and therefore it is rejected. The JSON response includes details regarding the detected virus by all antivirus engines used by the MetaDefender add-on.

{
  "public_id": "virus-test.txt",
  "version": 1531907346,
  "resource_type": "raw",
  "type": "upload",
  "placeholder": true,
  "created_at": "2018-07-18T09:49:06Z",
  "bytes": 0,
  "backup": true,
  "url": "http://res-staging.cloudinary.com/vladimirs/raw/upload/v1531907346/virus-test.txt",
  "secure_url": "https://res-staging.cloudinary.com/vladimirs/raw/upload/v1531907346/virus-test.txt",
  "moderation": [
    {
      "response": {
        "result_code": 1,
        "result": "Infected",
        "engine_results": [
          {
            "engine": "AegisLab",
            "result_code": 1,
            "threat_found": "EICAR-AV-Test"
          },
          {
            "engine": "Ahnlab",
            "result_code": 1,
            "threat_found": "EICAR_Test_File"
          },
          {
            ...
              additional engines here
            ...
          }    
        ]
      },
      "status": "rejected",
      "kind": "metascan",
      "updated_at": "2018-07-18T09:49:30Z"
    }
  ],
  "next_cursor": "01d821c4f994cfabaf7312de266312becbc9e449107006d2adc2195a58635c4f",
  "derived": []
}

Anti-malware moderation queue

Cloudinary's Admin API can be used to list the queue of all moderated assets of a specific type -- image (default), video, or raw. You can list all three queues of pending, approved and rejected assets by specifying the second parameter of the resources_by_moderation API method.

Ruby:
Cloudinary::Api.resources_by_moderation("metascan", "pending")
PHP:
$api = new \Cloudinary\Api();
$api->resources_by_moderation("metascan", "pending");
Python:
cloudinary.api.resources_by_moderation("metascan", "pending")
Node.js:
cloudinary.v2.api.resources_by_moderation('metascan', 'pending', 
  function(error, result){console.log(result);});
Java:
cloudinary.api().resourcesByModeration("metascan", "pending", 
  ObjectUtils.emptyMap());
.Net:
var listResourcesResult = cloudinary.Api.ListResourcesByModerationStatus("metascan", "pending"));
{
 "resources":
  [{
    "public_id": "q7vcvrfjm9mj4bfp3qc8",
    "format": "jpg",
    "version": 1393794403,
    "resource_type": "image",
    "type": "upload",
    "created_at": "2017-03-02T21:06:43Z",
    "bytes": 120253,
    "width": 864,
    "height": 576,
    "backup": true,
    "url": 
     "https://res.cloudinary.com/demo/image/upload/v1393794403/q7vcvrfjm9mj4bfp3qc8.jpg",
    "secure_url": 
     "https://res.cloudinary.com/demo/image/upload/v1393794403/q7vcvrfjm9mj4bfp3qc8.jpg"
   },
   {
    "public_id": "zp4fgdbabhlwwa7bxu84",
    "format": "jpg",
    "version": 1393794399,
    "resource_type": "image",
    "type": "upload",
    "created_at": "2017-03-02T21:06:39Z",
    "bytes": 120253,
    "width": 864,
    "height": 576,
    "backup": true,
    "url": 
     "https://res.cloudinary.com/demo/image/upload/v1393794399/zp4fgdbabhlwwa7bxu84.jpg",
    "secure_url": 
     "https://res.cloudinary.com/demo/image/upload/v1393794399/zp4fgdbabhlwwa7bxu84.jpg"
   }
 ]
}

Manual override

While the automatic virus scanning of the MetaDefender add-on is very accurate, in some cases you may want to manually override the moderation decision. You can either approve a previously rejected asset or reject an approved one.

One way to manually override the moderation result is using Cloudinary's Media Library interface.

From the main Media Library screen, click the Moderation button to enter the Moderation Queue. Then select MetaDefender and choose the Pending, Approved, or Rejected list. Below we show files that were rejected by MetaDefender. Note that the thumbnail previews of rejected assets are hidden by default. You can click the asset card to open the Preview pane or double-click it to open the Edit pane. From there, you can click the option to view the rejected asset and click the Approve (or thumbs up) option to revert the decision and approve the rejected asset.

MetaDefender rejected list

The same goes for the list of approved assets. You can click on the thumbs down button to revert the decision and prevent a certain asset from being publicly available to your users. For example, you can see that the ruby_file.rb file has now been moved to the approved list.

MetaDefender approved list

You can alternatively use Cloudinary's Admin API to programmatically override a specific moderation result. The following sample code uses the update API method while specifying a public ID of a moderated image (default resource type) and setting the moderation_status parameter to either the approved or the rejected status.

Ruby:
Cloudinary::Api.update("hwepb67oxzh4lrigssld", 
  :moderation_status => "approved")
PHP:
$api = new \Cloudinary\Api();
$api->update("hwepb67oxzh4lrigssld", 
  array("moderation_status" => "approved"));
Python:
cloudinary.api.update("hwepb67oxzh4lrigssld",
  moderation_status = "approved")
Node.js:
cloudinary.v2.api.update("hwepb67oxzh4lrigssld", 
  { moderation_status: "approved" },
  function(error, result){console.log(result);});
Java:
cloudinary.api().update("hwepb67oxzh4lrigssld", 
  ObjectUtils.asMap("moderation_status", "approved"));
.Net:
var updateParams = new UpdateParams("hwepb67oxzh4lrigssld")
{
  ModerationStatus = "approved"
};
var updateResult = cloudinary.Update(updateParams);