Perception Point Malware Detection

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

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

Perception Point's Malware Detection add-on provides the fastest and most accurate next-generation detection and response to any content-borne attack that can infiltrate your website. The Perception Point add-on scans any user generated content uploaded, such as documents, images, videos and other files, well before they reach end users. Seven layers of static and dynamic engines perform deep scanning of every piece of content, uncovering any concealed attack or evasion technique. The patented, cloud-native add-on is easily deployed, is easy to use, has virtually zero scanning delay and limitless scale, and does not tamper or change the scanned content.

With Perception Point's 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, making sure that no virus or malicious code is delivered to your web and mobile viewers.

Getting started

Before you can use the Perception Point's Malware Detection add-on:

  • You must have a Cloudinary account. If you don't already have one, you can sign up for a free account.

  • Register for the add-on: make sure you're logged in to your account and then go to the Add-ons page. For more information about add-on registrations, see Registering for add-ons.

  • Keep in mind that many of the examples on this page use our SDKs. For SDK installation and configuration details, see the relevant SDK guide.

  • If you are new to Cloudinary, you may want to take a look at How to integrate Cloudinary in your app for a walk through on the basics of creating and setting up your account, working with SDKs, and then uploading, transforming and delivering assets.

Automatic file scanning flow

The following describes the basic flow of uploading and displaying moderated assets using Cloudinary and the Perception Point 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 Perception Point for asynchronous moderation in the background.
    • The asset is either approved or rejected by Perception Point'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 does not appear in your media library, but is backed up, consuming storage, so that it can be restored if necessary.
  • Manual override

    • Pending, approved and rejected assets can be listed programmatically using Cloudinary's API or interactively using the Media Library in your account console.
    • 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 perception_point:

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Uploader.upload("local_file.jpg", 
  :moderation => "perception_point")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->upload("local_file.jpg", 
  ["moderation" => "perception_point"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::upload("local_file.jpg", 
  ["moderation" => "perception_point"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.upload("local_file.jpg",
  moderation = "perception_point")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.upload("local_file.jpg", 
  { moderation: "perception_point" },
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().upload("local_file.jpg", 
  ObjectUtils.asMap("moderation", "perception_point"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var uploadParams = new ImageUploadParams() 
{
  File = new FileDescription(@"local_file.jpg"),
  Moderation = "perception_point"
};
var uploadResult = cloudinary.Upload(uploadParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams().setModeration("perception_point")
var mySig = MyFunction(params)  // your own function that returns a signature generated on your backend
params.setSignature(CLDSignature(signature: mySig.signature, timestamp: mySig.timestamp))
let request = cloudinary.createUploader().signedUpload(url: "local_file.jpg", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().upload("local_file.jpg")
  .option("moderation", "perception_point").dispatch();
Go (cloudinary-go 1.x):
Copy to clipboard
resp, err := cld.Upload.Upload(ctx, "local_file.jpg", uploader.UploadParams{
        Moderation: "perception_point"})
curl:
Copy to clipboard
curl https://api.cloudinary.com/v1_1/demo/image/upload -X POST -F 'file=@/path/to/local_file.jpg' -F 'moderation=perception_point' -F 'timestamp=173719931' -F 'api_key=436464676' -F 'signature=a781d61f86a6f818af'
cli:
Copy to clipboard
cld uploader upload "local_file.jpg" moderation="perception_point"

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 1.x):
Copy to clipboard
Cloudinary::Uploader.upload("virus_check.txt", 
  :resource_type => :raw,
  :moderation => "perception_point")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->upload("virus_check.txt", [
    "resource_type" => "raw", 
    "moderation" => "perception_point"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::upload("virus_check.txt", [
    "resource_type" => "raw", 
    "moderation" => "perception_point"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.upload("virus_check.txt", 
  resource_type = "raw",
  moderation = "perception_point")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.upload("virus_check.txt", 
  { moderation: "perception_point", 
    resource_type: "raw" },
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().upload("virus_check.txt",  
  ObjectUtils.asMap(
    "resource_type", "raw", 
    "moderation", "perception_point"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var uploadParams = new RawUploadParams() 
{
  File = new FileDescription(@"virus_check.txt"),
  Moderation = "perception_point"
};
var uploadResult = cloudinary.Upload(uploadParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams()
  .setModeration("perception_point")
  .setResourceType(.raw)
var mySig = MyFunction(params)  // your own function that returns a signature generated on your backend
params.setSignature(CLDSignature(signature: mySig.signature, timestamp: mySig.timestamp))
let request = cloudinary.createUploader().signedUpload(url: "virus_check.txt", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().upload("virus_check.txt")
  .option("moderation", "perception_point")
  .option("resource_type", "raw").dispatch();
Go (cloudinary-go 1.x):
Copy to clipboard
resp, err := cld.Upload.Upload(ctx, "virus_check.txt", uploader.UploadParams{
        ResourceType: "raw",
        Moderation:   "perception_point"})
curl:
Copy to clipboard
curl https://api.cloudinary.com/v1_1/demo/raw/upload -X POST -F 'file=@/path/to/virus_check.txt' -F 'moderation=perception_point' -F 'timestamp=173719931' -F 'api_key=436464676' -F 'signature=a781d61f86a6f818af'
cli:
Copy to clipboard
cld uploader upload "virus_check.txt" resource_type="raw" moderation="perception_point"

Tip
You can use upload presets to centrally define a set of upload options including add-on operations to apply, instead of specifying them in each upload call. You can define multiple upload presets, and apply different presets in different upload scenarios. You can create new upload presets in the Upload page of the Management Console settings or using the upload_presets Admin API method. From the Upload page of the console settings, you can also select default upload presets to use for image, video, and raw API uploads (respectively) as well as default presets for image, video, and raw uploads performed via the Media Library UI.

Learn more: Upload presets

The Perception Point 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 Perception Point moderation is in the pending status.

Copy to clipboard
{
  "public_id":"qtxkfkpmpiivr15bcrjh",
  "version":1571212827,
    ...
    ... 
 "secure_url":
  "https://res.cloudinary.com/demo/image/upload/v1571212827/qtxkfkpmpiivr15bcrjh.jpg",
 "moderation":[{"status":"pending", "kind":"perception_point"}],
   ...
}

Status notification

Due to the fact that the Perception Point 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 perception_point parameter, you can request a notification by adding the notification_url parameter to a public HTTP or HTTPS notification URL. Cloudinary sends a POST request to the specified endpoint when that Perception Point scanning finishes.

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Uploader.upload("local_file.jpg", 
  :moderation => "perception_point",
  :notification_url => "https://mysite.example.com/hooks",
  :public_id => "local_file")
PHP (cloudinary_php 2.x):
Copy to clipboard
$cloudinary->uploadApi()->upload("local_file.jpg", [
    "moderation" => "perception_point",
    "notification_url" => "https://mysite.example.com/hooks",
    "public_id" => "local_file"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
\Cloudinary\Uploader::upload("local_file.jpg", [
    "moderation" => "perception_point",
    "notification_url" => "https://mysite.example.com/hooks",
    "public_id" => "local_file"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader.upload("local_file.jpg",
  moderation = "perception_point",
  notification_url = "https://mysite.example.com/hooks",
  public_id = "local_file")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.uploader.upload("local_file.jpg", 
  { moderation: "perception_point",
    notification_url: "https://mysite.example.com/hooks",
    public_id: "local_file" },
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.uploader().upload("local_file.jpg",
  ObjectUtils.asMap(
    "moderation", "perception_point",
    "notification_url", "https://mysite.example.com/hooks",
    "public_id", "local_file"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var uploadParams = new ImageUploadParams() 
{
  File = new FileDescription(@"local_file.jpg"),
  Moderation = "perception_point",
  PublicId = "local_file",
  NotificationUrl = "https://mysite.example.com/hooks"
};
var uploadResult = cloudinary.Upload(uploadParams);
iOS (cloudinary 3.x):
Copy to clipboard
let params = CLDUploadRequestParams()
  .setModeration("perception_point")
  .setPublicId("local_file")
  .setNotificationUrl("https://mysite.example.com/hook")
var mySig = MyFunction(params)  // your own function that returns a signature generated on your backend
params.setSignature(CLDSignature(signature: mySig.signature, timestamp: mySig.timestamp))
let request = cloudinary.createUploader().signedUpload(url: "local_file.jpg", params: params)
Android (cloudinary-android 1.x):
Copy to clipboard
MediaManager.get().upload("local_file.jpg")
  .option("moderation", "perception_point")
  .option("notification_url", "https://mysite.example.com/hooks")
  .option("public_id", "local_file").dispatch();
Go (cloudinary-go 1.x):
Copy to clipboard
resp, err := cld.Upload.Upload(ctx, "local_file.jpg", uploader.UploadParams{
        Moderation:      "perception_point",
        NotificationURL: "https://mysite.example.com/hooks",
        PublicID:        "local_file"})
cli:
Copy to clipboard
cld uploader upload "local_file.jpg" moderation="perception_point" notification_url="https://mysite.example.com/hooks" public_id="local_file"

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:

Copy to clipboard
{
  { 
    "moderation_response": 
    {
        "moderation_status": "approved",
        "moderation_kind": "perception_point",
        "moderation_updated_at": "2022-04-18T13:13:19Z",
        "asset_id": "13172eb3ea7065058777039e6c689",

          ...
            additional details from perception point
          ...

        "notification_type": "moderation"
    }
  }
}
Copy to clipboard
 {
  "moderation_response": {
    "decisions": [
      {
        "decision_verbose_name": "Eicar file detected",
        "scan_id": "450_1_a90d1252-2181-456c-b007-822960a6e06a_20220508",
        "description": "",
        "decision_version": 1,
        "timestamp": 1652016376.842498,
        "confidence": 1,
        "expose_level": 0.1,
        "warning_text": "Eicar file (pseudo-malware detected by all anti-viruses)",
        "verdict": "MAL",
        ...
          additional details from perception point
        ...
      }    
    ],
    "moderation_status": "rejected",
    "moderation_kind": "perception_point",
    "moderation_updated_at": "2022-04-18T14:06:58Z",
    "asset_id": "787172eb3ea70650458777039e6c6862",
      ...
      ...
    "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 1.x):
Copy to clipboard
Cloudinary::Api.resource('blue_suit')
PHP (cloudinary_php 2.x):
Copy to clipboard
$api->asset("blue_suit");
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
$api->resource("blue_suit");
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.api.resource("blue_suit")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.api.resource('blue_suit', 
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.api().resource("blue_suit", 
  ObjectUtils.emptyMap());
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var getResourceResult = cloudinary.GetResource(new GetResourceParams("blue_suit"));
Go (cloudinary-go 1.x):
Copy to clipboard
resp, err := cld.Admin.Asset(ctx, admin.AssetParams{PublicID: "blue_suit"})
cli:
Copy to clipboard
cld admin resource "blue_suit"

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.

Copy to clipboard
{
   "asset_id": "dec0e2a273f3c089a980dbe984933861",
   "public_id":"blue_suit",
   "format":"jpg",
   "version":1531919587,
   "resource_type":"image",
   "type":"upload",
   "created_at":"2018-07-18T13:13:07Z",
      ...
   "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":
       {
          ...
            additional details from perception point
          ...
       },
      "status":"approved",
      "kind":"perception_point",
      "updated_at":"2018-07-18T13:13:19Z"
    }],
   "next_cursor":  "2c9c91253385d01bf4e2293b64a9dc9c4aa5904b4e78a0bb107b99ae0a851dd7",
   "derived":[]
}

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

Ruby (cloudinary 1.x):
Copy to clipboard
Cloudinary::Api.resource('virus-test.txt', :resource_type : "raw")
PHP (cloudinary_php 2.x):
Copy to clipboard
$api->asset("virus-test.txt", ["resource_type" => "raw"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
$api->resource("virus-test.txt", ["resource_type" => "raw"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.api.resource("virus-test.txt", resource_type = "raw")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.api.resource('virus-test.txt', 
  { resource_type: "raw" },
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.api().resource("virus-test.txt", 
  ObjectUtils.asMap("resource_type", "raw"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var getResourceParams = new GetResourceParams("virus-test.txt")
{
  ResourceType = "raw"
}
var getResourceResult = cloudinary.GetResource(getResourceParams);
Go (cloudinary-go 1.x):
Copy to clipboard
resp, err := cld.Admin.Asset(ctx, admin.AssetParams{
        PublicID:  "virus_test.txt",
        AssetType: "raw"})
cli:
Copy to clipboard
cld admin resource "virus-test.txt" resource_type="raw"

This file contains a virus signature and therefore it is rejected. The JSON response includes details regarding the detected virus by the Perception Point add-on.

Copy to clipboard
{
  "public_id": "virus-test.txt",
  "version": 1571213865,
  "resource_type": "raw",
  "type": "upload",
  "placeholder": true,
  "created_at": "2018-07-18T09:49:06Z",
      ...
  "url": "http://res.cloudinary.com/demo/raw/upload/v1571213865/virus-test.txt",
  "secure_url": "https://res.cloudinary.com/demo/raw/upload/v1571213865/virus-test.txt",
  "moderation": [
    {
      "response": {
        ...
            additional details from perception point
        ...
      },
      "status": "rejected",
      "kind": "perception_point",
      "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 1.x):
Copy to clipboard
Cloudinary::Api.resources_by_moderation("perception_point", "pending")
PHP (cloudinary_php 2.x):
Copy to clipboard
$api->assetsByModeration("perception_point", "pending");
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
$api->resources_by_moderation("perception_point", "pending");
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.api.resources_by_moderation("perception_point", "pending")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.api.resources_by_moderation('perception_point', 'pending', 
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.api().resourcesByModeration("perception_point", "pending", 
  ObjectUtils.emptyMap());
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var listResourcesResult = cloudinary.Api.ListResourcesByModerationStatus("perception_point", "pending"));
Go (cloudinary-go 1.x):
Copy to clipboard
resp, err := cld.Admin.AssetsByModeration(ctx, admin.AssetsByModerationParams{
        Kind:   "perception_point",
        Status: "pending"})
cli:
Copy to clipboard
cld admin resources_by_moderation "perception_point" "pending"
Copy to clipboard
{
 "resources":
  [{
    "public_id": "q7vcvrfjm9mj4bfp3qc8",
    "format": "jpg",
    "version": 1570976812,
    "resource_type": "image",
    "type": "upload",
    "created_at": "2022-03-02T21:06:43Z",
    "bytes": 120253,
    "width": 864,
    "height": 576,
    "backup": true,
    "url": 
     "https://res.cloudinary.com/demo/image/upload/v1570976812/q7vcvrfjm9mj4bfp3qc8.jpg",
    "secure_url": 
     "https://res.cloudinary.com/demo/image/upload/v1570976812/q7vcvrfjm9mj4bfp3qc8.jpg"
   },
   {
    "public_id": "zp4fgdbabhlwwa7bxu84",
    "format": "jpg",
    "version": 1570977154,
    "resource_type": "image",
    "type": "upload",
    "created_at": "2022-03-02T21:06:39Z",
    "bytes": 120253,
    "width": 864,
    "height": 576,
    "backup": true,
    "url": 
     "https://res.cloudinary.com/demo/image/upload/v1570977154/zp4fgdbabhlwwa7bxu84.jpg",
    "secure_url": 
     "https://res.cloudinary.com/demo/image/upload/v1570977154/zp4fgdbabhlwwa7bxu84.jpg"
   }
 ]
}

Manual override

While the automatic virus scanning of the Perception Point 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 Web interface. From the left navigation menu, select Moderation. Then, from the moderation tools list in the top menu, select Perception Point and then select which moderation queue to display (Pending, Rejected, or Approved).

  • When listing the queue rejected by Perception Point, you can click on the green Approve button to revert the decision and recover the original rejected asset.
  • When listing the queue approved by Perception Point, you can click on the red Reject button to revert the decision and prevent a certain asset from being publicly available to your users.

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 1.x):
Copy to clipboard
Cloudinary::Api.update("hwepb67oxzh4lrigssld", 
  :moderation_status => "approved")
PHP (cloudinary_php 2.x):
Copy to clipboard
$api->update("hwepb67oxzh4lrigssld", 
  ["moderation_status" => "approved"]);
PHP (cloudinary_php 1.x (legacy)):
Copy to clipboard
$api->update("hwepb67oxzh4lrigssld", 
  ["moderation_status" => "approved"]);
Python (cloudinary 1.x):
Copy to clipboard
cloudinary.api.update("hwepb67oxzh4lrigssld",
  moderation_status = "approved")
Node.js (cloudinary 1.x):
Copy to clipboard
cloudinary.v2.api.update("hwepb67oxzh4lrigssld", 
  { moderation_status: "approved" },
  function(error, result){console.log(result);});
Java (cloudinary 1.x):
Copy to clipboard
cloudinary.api().update("hwepb67oxzh4lrigssld", 
  ObjectUtils.asMap("moderation_status", "approved"));
.NET (CloudinaryDotNet 1.x):
Copy to clipboard
var updateParams = new UpdateParams("hwepb67oxzh4lrigssld")
{
  ModerationStatus = "approved"
};
var updateResult = cloudinary.Update(updateParams);
Go (cloudinary-go 1.x):
Copy to clipboard
resp, err := cld.Admin.UpdateAsset(ctx, admin.UpdateAssetParams{
        PublicID:         "hwepb67oxzh4lrigssld",
        ModerationStatus: "approved"})
cli:
Copy to clipboard
cld admin update "hwepb67oxzh4lrigssld" moderation_status="approved"

✔️ Feedback sent!

Rate this page: