Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions iped-app/resources/config/IPEDConfig.txt
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,8 @@ enableHTMLReport = true
# It performs classification of image and video files.
# Advanced settings can be modified in file "conf/RemoteImageClassifierConfig.txt"
enableRemoteImageClassifier = false

# Enables remote external image classifier.
# It performs classification of image and video files using review priority and embeddings output.
# Advanced settings can be modified in file "conf/RemoteImageExternalClassifierConfig.txt"
enableRemoteImageExternalClassifier = true

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new task is enabled by default (enableRemoteImageExternalClassifier = true), while the existing similar task enableRemoteImageClassifier is disabled by default (false), and all other optional/external-service-dependent features in IPEDConfig.txt (PhotoDNA, OCR, face recognition, remote image classifier, etc.) are also disabled by default. Enabling this task by default means every user who does not have the external service running will encounter a connection error at startup (which only logs a warning and disables the task). This is inconsistent with the project's convention of shipping optional features disabled. It should be set to false by default.

Suggested change
enableRemoteImageExternalClassifier = true
enableRemoteImageExternalClassifier = false

Copilot uses AI. Check for mistakes.
8 changes: 8 additions & 0 deletions iped-app/resources/config/conf/AIFiltersConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
{"name": "Other"}
]},

{"name": "External CSAM Detector", "prefix": "ExternalCSAM", "property": "ai\\:externalClassifier\\:reviewPriority", "value": "*", "children":[
{"name": "Very High", "value": "VERY_HIGH"},
{"name": "High", "value": "HIGH"},
{"name": "Medium", "value": "MEDIUM"},
{"name": "Low", "value": "LOW"},
{"name": "Very Low", "value": "VERY_LOW"}
]},

{"name": "Detected Faces", "prefix": "Face", "property": "face\\_count", "value": "[1 TO *]", "children":[
{"name": "1"},
{"name": "2"},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
##########################################
# RemoteImageExternalClassifier Task configuration
##########################################

# URL of the service/central node used by the RemoteImageExternalClassifier implementation
url = http://localhost:8000

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default url = http://localhost:8000 for RemoteImageExternalClassifier means the thumbnails ZIP is sent over plain HTTP whenever the "service/central node" is not truly local, exposing sensitive evidence to eavesdropping or tampering on the network. Because RemoteImageExternalClassifierTask uses config.getUrl() verbatim to build urlZip/urlVersion, an attacker on the path can read or modify the ZIP; default this to an https:// URL and document that TLS is required for any non-local deployments.

Suggested change
url = http://localhost:8000
# For any non-local deployment, this endpoint MUST use HTTPS/TLS.
url = https://localhost:8000

Copilot uses AI. Check for mistakes.

# Maximum number of thumbs to be included in the zip file to send to the server
batchSize = 50

# Optional: used only if backend also returns legacy "class" probabilities.
# Minimum confidence threshold for assigning a class label to an image/video. Values are between 0 and 100.
labelingThreshold = 50

# Skip classification of images/videos smaller than a given file size (in bytes; '0' = do not skip)
skipSize = 2048

# Skip classification of images/videos smaller than a given dimension, i.e. height or width (in pixels; '0' = do not skip)
skipDimension = 48

# Skip classification of images/videos with hits on IPED hashesDB database (if 'hashesDB' is not configured in 'LocalConfig.txt' or 'false', do not skip)
skipHashDBFiles = true

# Validate server SSL certificate
validateSSL = false

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting validateSSL = false by default disables TLS certificate and hostname validation in RemoteImageExternalClassifierTask.getClient(), which relies on a trust-all TrustStrategy and NoopHostnameVerifier. This allows any host with a spoofed certificate on the network path to impersonate the classifier service and read or alter the evidence ZIP; make this flag default to true and avoid using the trust-all branch outside tightly controlled testing.

Suggested change
validateSSL = false
validateSSL = true

Copilot uses AI. Check for mistakes.
1 change: 1 addition & 0 deletions iped-app/resources/config/conf/TaskInstaller.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<task class="iped.engine.task.PhotoDNATask"></task>
<task class="iped.engine.task.PhotoDNALookup"></task>
<task class="iped.engine.task.RemoteImageClassifierTask"></task>
<task class="iped.engine.task.RemoteImageExternalClassifierTask"></task>
<task script="NSFWNudityDetectTask.py"></task>
<task script="FaceRecognitionTask.py"></task>
<task script="AgeEstimationTask.py"></task>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package iped.engine.config;

import iped.utils.UTF8Properties;

public class RemoteImageExternalClassifierConfig extends AbstractTaskPropertiesConfig {

private static final long serialVersionUID = 1L;

/**
* Config file name and enable/disable property.
*/
private static final String CONFIG_FILE = "RemoteImageExternalClassifierConfig.txt";
private static final String ENABLE_PROP = "enableRemoteImageExternalClassifier";

/**
* Constants mapping to properties name in config file.
*/
private static final String URL = "url";
private static final String BATCH_SIZE = "batchSize";
private static final String LABELING_THRESHOLD = "labelingThreshold";
private static final String SKIP_SIZE = "skipSize";
private static final String SKIP_DIMENSION = "skipDimension";
private static final String SKIP_HASH_DB_FILES = "skipHashDBFiles";
private static final String VALIDATE_SSL = "validateSSL";

// URL of the service/central node used by the RemoteImageClassifier implementation
private String url;

// Maximum number of thumbs to be included in the zip file to send to the server
private int batchSize = 50;

// Threshold used to decide if an image is labeled with one class
private double labelingThreshold = 60;

// Skip classification of images/videos smaller than a given file size (in bytes; '0' = do not skip)
private int skipSize = 0;

// Skip classification of images/videos smaller than a given dimension, i.e. height or width (in pixels; '0' = do not skip)
private int skipDimension = 0;

// Skip classification of images/videos with hits on IPED hashesDB database (if 'hashesDB' is not configured in 'LocalConfig.txt' or 'false', do not skip)
private boolean skipHashDBFiles = true;

// Validate server SSL certificate
private boolean validateSSL = false;

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default for validateSSL is false in both the Java config class and the config file. When validateSSL = false, the HTTP client trusts all SSL certificates and disables hostname verification entirely. Since this task handles CSAM data — which is highly sensitive — the default should be true to enforce proper SSL validation and prevent man-in-the-middle attacks. Users who need self-signed certificates in a controlled environment can explicitly set it to false.

Suggested change
private boolean validateSSL = false;
private boolean validateSSL = true;

Copilot uses AI. Check for mistakes.

@Override
public String getTaskEnableProperty() {
return ENABLE_PROP;
}

@Override
public String getTaskConfigFileName() {
return CONFIG_FILE;
}

public String getUrl() {
return url;
}

public int getBatchSize() {
return batchSize;
}

public double getLabelingThreshold() {
return labelingThreshold;
}
Comment on lines +65 to +67

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The labelingThreshold property is defined and parsed in RemoteImageExternalClassifierConfig (with a getter getLabelingThreshold()), and also documented in RemoteImageExternalClassifierConfig.txt. However, the task RemoteImageExternalClassifierTask never reads or uses this value. Since this is described as "Optional: used only if backend also returns legacy 'class' probabilities", the field appears to be dead configuration. Either remove the unused property from the config and config file, or document clearly that it will be used in a future version.

Copilot uses AI. Check for mistakes.

public int getSkipSize() {
return skipSize;
}

public int getSkipDimension() {
return skipDimension;
}

public boolean isSkipHashDBFiles() {
return skipHashDBFiles;
}

public boolean isValidateSSL() {
return validateSSL;
}

@Override
void processProperties(UTF8Properties properties) {

String value = properties.getProperty(URL);
if (value != null && !value.trim().isEmpty())
url = value.trim();

value = properties.getProperty(BATCH_SIZE);
if (value != null && !value.trim().isEmpty()) {
batchSize = Integer.valueOf(value.trim());
// enforce minimum value
if (batchSize < 1)
batchSize = 1;
}

value = properties.getProperty(LABELING_THRESHOLD);
if (value != null && !value.trim().isEmpty()) {
labelingThreshold = Double.parseDouble(value.trim());
// enforce range [0, 100]
if (labelingThreshold < 0) {
labelingThreshold = 0;
} else if (labelingThreshold > 100) {
labelingThreshold = 100;
}
}

value = properties.getProperty(SKIP_SIZE);
if (value != null && !value.trim().isEmpty()) {
skipSize = Integer.valueOf(value.trim());
// enforce minimum value
if (skipSize < 0)
skipSize = 0;
}

value = properties.getProperty(SKIP_DIMENSION);
if (value != null && !value.trim().isEmpty()) {
skipDimension = Integer.valueOf(value.trim());
// enforce minimum value
if (skipDimension < 0)
skipDimension = 0;
}

value = properties.getProperty(SKIP_HASH_DB_FILES);
if (value != null && !value.trim().isEmpty())
skipHashDBFiles = Boolean.valueOf(value.trim());

value = properties.getProperty(VALIDATE_SSL);
if (value != null && !value.trim().isEmpty())
validateSSL = Boolean.valueOf(value.trim());

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import iped.engine.config.IndexTaskConfig;
import iped.engine.task.HashTask;
import iped.engine.task.PhotoDNATask;
import iped.engine.task.RemoteImageExternalClassifierTask;
import iped.engine.task.index.IndexItem;
import iped.localization.LocalizedProperties;
import iped.properties.ExtraProperties;
Expand Down Expand Up @@ -57,6 +58,8 @@ public static Analyzer get() {
analyzerPerField.put(IndexItem.CHANGED, new KeywordAnalyzer());
analyzerPerField.put(IndexItem.TIMESTAMP, new KeywordAnalyzer());

analyzerPerField.put(RemoteImageExternalClassifierTask.AI_REVIEW_PRIORITY_ATTR, new KeywordAnalyzer());

IndexTaskConfig indexConfig = ConfigurationManager.get().findObject(IndexTaskConfig.class);
StandardASCIIAnalyzer hashAnalyzer = new StandardASCIIAnalyzer();
hashAnalyzer.setMaxTokenLength(Integer.MAX_VALUE);
Expand Down
Loading
Loading