Back to skill

Security audit

Didit Face Match

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it uploads sensitive facial images to Didit with weak consent, retention, and output-minimization controls.

Review this skill carefully before installing. Use it only when you have permission to process the facial images, understand Didit's retention and deletion terms, and are comfortable sending images and derived biometric metadata to an external service. Prefer changing the script to disable request retention by default, print only status and score, and require explicit consent before upload.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/match_faces.py:35
Finding

Biometric API Requests Are Retained by Default

Content
View full analysis

Vulnerability Details

File Location: scripts/match_faces.py:35-49
Related Documentation: SKILL.md:65
Vulnerability Type: Biometric data retention enabled through an omitted privacy parameter
Risk Level: Medium

Vulnerable Code

python
headers = {"x-api-key": api_key}
data = {
    "face_match_score_decline_threshold": str(threshold),
    "rotate_image": str(rotate).lower(),
}
if vendor_data:
    data["vendor_data"] = vendor_data

with open(user_image, "rb") as uf, open(ref_image, "rb") as rf:
    files = {
        "user_image": (os.path.basename(user_image), uf),
        "ref_image": (os.path.basename(ref_image), rf),
    }
    response = requests.post(API_URL, headers=headers, files=files,
                             data=data, timeout=60)

The Skill documentation states that the omitted option defaults to retention:

markdown
| `save_api_request` | boolean | No | `true` | — | Save in Business Console Manual Checks |

Technical Analysis

The face-matching operation necessarily transmits two facial images to the declared Didit HTTPS endpoint. However, the request does not set save_api_request to false. According to the bundled API documentation, this parameter defaults to true, causing the request to be saved in the Business Console Manual Checks area.

Persistent server-side retention is not necessary to obtain a standalone face-match result. The behavior therefore exceeds the minimum data privileges required for the declared functionality and conflicts with the Skill's own recommendation to minimize biometric image storage.

Facial images are persistent biometric identifiers that cannot be safely rotated in the same manner as passwords or API keys. Retaining them unnecessarily increases the consequences of account compromise, excessive console access, insider misuse, or a service-side data breach.

Attack Path

  1. A user supplies a selfie and ...[truncated 1314 chars]
Remediation
View remediation

Remediation Suggestions

Add the privacy control explicitly to every request:

python
data = {
    "face_match_score_decline_threshold": str(threshold),
    "rotate_image": str(rotate).lower(),
    "save_api_request": "false",
}

If request retention is required for a specific workflow:

  1. Keep retention disabled by default.
  2. Introduce an explicit option such as --save-api-request.
  3. Display a clear notice that enabling the option may persist facial images and associated metadata in the Business Console.
  4. Obtain informed user authorization before uploading with retention enabled.
  5. Document the retention duration, access controls, deletion process, and applicable data-processing terms.
  6. Restrict Business Console access using least privilege and periodically remove records that are no longer required.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/match_faces.py:70
Finding

Complete Biometric Analysis Response Is Written to Standard Output

Content
View full analysis

Vulnerability Details

File Location: scripts/match_faces.py:70
Related Documentation: SKILL.md:83-108,146
Vulnerability Type: Excessive disclosure of sensitive biometric metadata
Risk Level: Low

Vulnerable Code

python
print(json.dumps(result, indent=2, ensure_ascii=False))

Technical Analysis

The script serializes and prints the complete API response rather than limiting output to the match status and score. Based on the documented response schema, the full response may include:

  • Estimated age
  • Estimated gender
  • Face bounding boxes
  • Face-detection confidence values
  • Image rotation information
  • Request identifiers
  • Warning and risk information
  • Other service-provided biometric metadata

Standard output is commonly captured by shell redirection, CI/CD systems, job runners, agent transcripts, notebook environments, and centralized logging platforms. Consequently, data that is not required by the default command-line result can be copied into additional storage systems with different retention periods and access controls.

This behavior conflicts with the documentation's security recommendation to store only the status and score. Although printing output is expected for a command-line utility, printing all derived biometric fields by default is not necessary for the declared matching task.

Attack Path

  1. A user invokes the script with two facial images.
  2. Didit returns the face-match result and derived biometric metadata.
  3. The script serializes the entire response to standard output.
  4. An execution environment captures standard output in a terminal log, CI artifact, agent transcript, or centralized logging service.
  5. A user with access to that secondary system retrieves biometric metadata that would not have been exposed if only the status and score had been printed.

No additional operating-system or application privilege is obtained directly through ...[truncated 871 chars]

Remediation
View remediation

Remediation Suggestions

Print only the minimum result required by default:

python
fm = result.get("face_match", {})
output = {
    "status": fm.get("status", "Unknown"),
    "score": fm.get("score"),
}
print(json.dumps(output, indent=2, ensure_ascii=False))

For callers that genuinely require the full response:

  1. Add an explicit --full-response option.
  2. Warn that the full output may contain sensitive biometric metadata.
  3. Keep minimal status-and-score output as the default.
  4. Advise users not to enable verbose output in shared terminals or persist it in CI and centralized logs.
  5. Review downstream logging and retention controls before exposing complete responses.
  6. Avoid logging the API key, original image content, or local image paths in future error-handling changes.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Tainted flow: 'headers' from os.environ.get (line 35, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/match_faces.py (reported line 48)May include surrounding context.

python
"user_image": (os.path.basename(user_image), uf),
            "ref_image": (os.path.basename(ref_image), rf),
        }
        response = requests.post(API_URL, headers=headers, files=files,
                                 data=data, timeout=60)

    if response.status_code != 200:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill requires environment access and network egress but does not declare an explicit tool scope or allowed-tools boundary. That weakens least-privilege controls and can let an agent invoke capabilities not clearly disclosed to operators, especially problematic for a skill that handles sensitive biometric data and API keys.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill sends facial images to a third-party API but does not provide an up-front privacy warning or explicit consent guidance before describing transmission. Because facial images are highly sensitive biometric data, omission of this warning can lead to silent disclosure of regulated personal data and inappropriate downstream retention or processing.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill advertises age estimation, gender detection, and face bounding boxes in addition to matching, which expands processing into sensitive attribute inference beyond the stated primary purpose. That creates privacy and compliance risk because users may expect identity comparison only, not demographic inference or extra biometric metadata extraction.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manifest presents the skill as a face-match integration, but the body also instructs account registration, email verification, and billing top-up workflows. This scope expansion can mislead users and reviewers, increasing the chance an agent performs unexpected account-management or payment-related actions under the guise of a biometric comparison skill.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
96% confidence
Finding

This example performs external transmission of face images and an API key to a third-party endpoint. In context, that is the core function of the skill, but it is still security-relevant because it moves sensitive biometric data off-platform to an external service where confidentiality, consent, and retention controls matter greatly.

Content

Scanner excerpt · SKILL.md (reported line 86)May include surrounding context.

python
import requests

response = requests.post(
    "https://verification.didit.me/v3/face-match/",
    headers={"x-api-key": "YOUR_API_KEY"},
    files={

External Transmission

Medium
Category
Data Exfiltration
Confidence
96% confidence
Finding

The JavaScript example likewise uploads biometric facial images to an external API endpoint. Although expected for this integration, the transfer remains dangerous without strong disclosure and control because it exposes sensitive personal data and authentication material to a remote service outside the local trust boundary.

Content

Scanner excerpt · SKILL.md (reported line 103)May include surrounding context.

md
formData.append("ref_image", referenceFile);
formData.append("face_match_score_decline_threshold", "50");

const response = await fetch("https://verification.didit.me/v3/face-match/", {
  method: "POST",
  headers: { "x-api-key": "YOUR_API_KEY" },
  body: formData,

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
88% confidence
Finding

The score interpretation table recommends 'Auto-approve' for high similarity results, encouraging fully automated identity decisions based on biometric scoring alone. That can create security, fairness, and compliance issues because false positives, spoofing edge cases, or poor-quality images may lead to unjustified approvals without human review or additional signals.

Content

Scanner excerpt · SKILL.md (reported line 185)May include surrounding context.

md
| Score Range | Interpretation | Action |
|---|---|---|
| 90-100 | Very high confidence — same person | Auto-approve |
| 70-89 | High confidence — likely same person | Approve (default threshold 30) |
| 50-69 | Moderate — possible match | Consider manual review |
| 30-49 | Low — likely different people | Declined at default threshold |

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This script transmits facial images, which are highly sensitive biometric data, to a third-party external service without any explicit runtime warning, consent prompt, or privacy notice. In the context of a face-matching skill, this data flow is functionally expected, but the absence of explicit disclosure increases privacy, compliance, and user-trust risk, especially where biometric processing requires informed consent.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.