Back to skill

Security audit

Didit Biometric Age Estimation

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to perform the advertised Didit age-estimation workflow, but it handles face images with retention and logging risks that users should review before use.

Review this skill before installing or using it with real users. Only process face images with informed consent and legal authorization, consider modifying requests to set save_api_request=false by default, avoid logging full API responses, minimize vendor_data, use a limited Didit API key, and pin dependencies in an isolated environment.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/estimate_age.py:30
Finding

Biometric request retention is enabled by default

Content
View full analysis

Vulnerability Details

File Location: scripts/estimate_age.py:30-48; retention default documented at SKILL.md:85
Vulnerability Type: Failure to minimize biometric data retention
Risk Level: Medium

Evidence

The API documentation states that omitted save_api_request values default to true:

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

The request does not override that default:

python
def estimate_age(image_path: str, rotate: bool = False, vendor_data: str = None) -> dict:
    api_key = get_api_key()
    with open(image_path, "rb") as f:
        files = {"user_image": (os.path.basename(image_path), f, "image/jpeg")}
        data = {}
        if rotate:
            data["rotate_image"] = "true"
        if vendor_data:
            data["vendor_data"] = vendor_data
        r = requests.post(ENDPOINT, headers={"x-api-key": api_key},
                          files=files, data=data, timeout=60)
    if r.status_code not in (200, 201):
        print(f"Error {r.status_code}: {r.text}", file=sys.stderr)
        sys.exit(1)
    return r.json()

Technical Analysis

The Skill must transmit a facial image to the declared Didit service to perform cloud-based age estimation. That transmission is necessary for the stated functionality. Retaining the request in the Didit Business Console, however, is not required to calculate the result.

Because the script omits save_api_request, the service applies its documented default of true. Users therefore receive no explicit choice before their facial image and associated request are retained. Facial images are sensitive biometric data, so this behavior violates data-minimization and least-retention principles.

Attack Path

  1. A user invokes the script with a facial image.
  2. The script uploads the image without setting save_api_request.
  3. The service applies the ...[truncated 622 chars]
Remediation
View remediation

Remediation Suggestions

Set save_api_request to false on every request by default:

python
data = {"save_api_request": "false"}

If retention is operationally necessary, expose it through an explicit opt-in option such as --save-api-request. Before enabling it, clearly disclose what data will be retained, who can access it, the retention duration, and how deletion can be requested. Apply least-privilege access controls and short retention policies in the Business Console.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/estimate_age.py:67
Finding

Complete API response may expose temporary biometric-media URLs in logs

Content
View full analysis

Vulnerability Details

File Location: scripts/estimate_age.py:67; sensitive response fields documented at SKILL.md:121-122
Vulnerability Type: Sensitive information exposure through standard output
Risk Level: Medium

Evidence

The Skill documentation identifies temporary biometric-media URLs as response fields:

markdown
| `reference_image` | string | Temporary URL (expires 60 min) |
| `video_url` | string | Temporary URL for active liveness video. `null` for passive |

The script prints the complete response without filtering or redaction:

python
result = estimate_age(args.image, args.rotate, args.vendor_data)
print(json.dumps(result, indent=2))

age_est = result.get("age_estimation", {})
estimated_age = age_est.get("estimated_age")
status = age_est.get("status", "Unknown")
print(f"\n--- Estimated age: {estimated_age} | Status: {status} ---")

Technical Analysis

Printing the entire API response can disclose reference_image, video_url, request identifiers, warning data, and other service metadata. Temporary URLs may function as bearer-style access links: possession of the URL may be sufficient to retrieve the referenced media until it expires.

Standard output is frequently captured by CI/CD systems, job runners, terminal recording, support tooling, container logs, or centralized monitoring. The CLI only needs the estimated age and status for its stated behavior, so printing all response fields exceeds minimum necessary disclosure.

Attack Path

  1. The script submits a facial image and receives an API response containing a temporary media URL.
  2. json.dumps(result, indent=2) writes the complete response to standard output.
  3. A shell wrapper, CI system, scheduler, or monitoring service captures that output.
  4. Another user or attacker with access to the captured output obtains the URL.
  5. If the URL remains valid and does not require additional authorizatio ...[truncated 427 chars]
Remediation
View remediation

Remediation Suggestions

Print only fields required for normal operation, such as the estimated age and status. Explicitly remove or redact reference_image, video_url, and identifying metadata before serialization.

If complete output is needed for troubleshooting, place it behind an explicit option such as --verbose or --output-json, warn that the output may contain biometric-media URLs, and recommend writing it only to a permission-restricted destination. Avoid emitting sensitive API responses into shared CI or centralized logs.

T08 · Insecure Dependencies

Note
Location
SKILL.md:240
Finding

Dependency installation instruction does not pin or verify requests

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:240
Vulnerability Type: Unpinned third-party dependency
Risk Level: Low

Evidence

The installation guidance retrieves an unconstrained package version:

bash
# Requires: pip install requests

Technical Analysis

Installing requests without a version constraint, lockfile, or integrity hash resolves whichever compatible release and transitive dependencies are available at installation time. The referenced package name is legitimate and no dependency-confusion or typosquatting behavior was found, but the instruction produces non-reproducible environments and accepts future dependency changes without project review.

This is a supply-chain hardening weakness rather than evidence that the current requests package is malicious.

Attack Path

  1. A user follows the documented pip install requests instruction.
  2. The package installer resolves mutable versions from its configured package index.
  3. A future compromised, substituted, or unexpectedly incompatible package release is downloaded.
  4. Package installation hooks or imported package code execute with the privileges of the user running the command.

Exploitation requires compromise or malicious substitution in the configured dependency supply chain; the audited project itself does not perform such substitution.

Impact Assessment

If the resolved dependency were compromised, its code could run with the invoking Python process's user privileges and access data available to that process, potentially including DIDIT_API_KEY and facial images processed by the script. No direct compromise is demonstrated by the audited files, so the current risk level is low.

Remediation
View remediation

Remediation Suggestions

Provide a reviewed dependency manifest with an exact version and hashes, for example a hash-locked requirements.txt generated by a dependency-management tool. Install dependencies in an isolated virtual environment and periodically update the pinned version after security review and automated vulnerability scanning.

Document use of the official Python Package Index or an organization-controlled package mirror, and avoid disabling TLS verification or package-integrity controls.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Tainted flow: 'api_key' from os.environ.get (line 26, 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/estimate_age.py (reported line 42)May include surrounding context.

python
data["rotate_image"] = "true"
        if vendor_data:
            data["vendor_data"] = vendor_data
        r = requests.post(ENDPOINT, headers={"x-api-key": api_key},
                          files=files, data=data, timeout=60)
    if r.status_code not in (200, 201):
        print(f"Error {r.status_code}: {r.text}", file=sys.stderr)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The skill description promises compliance-oriented controls such as passive liveness, adaptive fallback to ID verification, and per-country restrictions, but the documented implementation appears to only submit an image and locally interpret the response. This mismatch can cause integrators to rely on safeguards that are not actually enforced, leading to underage access, failed compliance checks, or insecure deployment assumptions.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill requires environment access and makes outbound network calls but does not declare any explicit tool scope or allowed-tools restrictions. That weakens least-privilege controls and can let the skill run with broader capabilities than users or the platform may expect, which is risky for a skill handling biometric images and API credentials.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill explicitly sends facial images to a third-party biometric service but does not provide a clear privacy, consent, or data-handling warning. Because biometric data is highly sensitive, missing disclosure increases the risk of unauthorized collection, regulatory violations, and unsafe use by downstream developers.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The documentation shows save_api_request defaults to true, meaning submitted biometric requests may be stored in the provider's console, but this retention behavior is not clearly highlighted as a privacy and security concern. Silent default retention of facial images and identifiers expands exposure in the event of misuse, over-collection, or provider-side compromise.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
94% confidence
Finding

The example code performs external transmission of a facial image and vendor identifier to a third-party API. In this skill context that behavior is expected, but it is still security-relevant because it involves off-platform transfer of sensitive biometric data and associated identifiers to an external service.

Content

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

python
import requests

response = requests.post(
    "https://verification.didit.me/v3/age-estimation/",
    headers={"x-api-key": "YOUR_API_KEY"},
    files={"user_image": ("selfie.jpg", open("selfie.jpg", "rb"), "image/jpeg")},

External Transmission

Medium
Category
Data Exfiltration
Confidence
94% confidence
Finding

The TypeScript example also transmits a user selfie to the external Didit API. Although this matches the skill's intended function, it remains a true security/privacy concern because biometric information leaves the local environment and is processed by a third party.

Content

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

md
const formData = new FormData();
formData.append("user_image", selfieFile);

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

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script transmits a facial image and optional vendor tracking data to a third-party biometric API, but it does not provide an explicit disclosure, consent prompt, or privacy guardrails before sending highly sensitive personal data. In a biometric age-estimation skill, this is especially sensitive because face images are regulated in many jurisdictions and misuse can create privacy, compliance, and user-trust risks.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.