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## 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
## 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## 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
## 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## 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
## 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
Content
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)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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.

Lp3

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.

Missing User Warnings

Medium
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.

Missing User Warnings

Medium
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.

External Transmission

Medium
Category
Data Exfiltration
Content
```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")},
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.

External Transmission

Medium
Category
Data Exfiltration
Content
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,
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.

Missing User Warnings

Medium
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.

Static analysis

No suspicious patterns detected.