Forensic media triage with chain of custody. Use when receiving images, videos, audio, PDFs, or documents that need evidence-grade handling, integrity verification, and audit trails.
EvidenceOps provides forensic-grade handling of media files with complete chain of custody:
Before using this skill, ensure:
@openclaw/evidence-vault is installed and initializedWhen media is received via any channel:
User sends: [image/video/document]
Required Information:
IF user specifies existing caseId:
USE that caseId
ELSE IF user requests new case:
CREATE case with format: case-{YYYY}-{NNN}
EXAMPLE: case-2026-001
ELSE:
ASK user: "Should I create a new case or add to existing case [case-2026-XXX]?"
Case ID Format: case-{year}-{sequence}
^case-[a-zA-Z0-9_-]+$case-2026-001, case-incident-alpha, case-legal-2026-q1Before ingest:
# Staging directory structure
/tmp/evidence-staging/
├── {caseId}/
│ └── {timestamp}-{filename}
Extract metadata WITHOUT modifying original:
For Images:
For Videos:
For Audio:
For PDFs:
# Use Read tool or appropriate extraction commands
# NEVER write back to original file
Create derivative artifacts in SEPARATE folder:
derivatives/
├── thumbnails/
│ └── {evidenceId}-thumb.jpg
├── transcripts/
│ └── {evidenceId}-transcript.txt
└── previews/
└── {evidenceId}-preview.pdf
Derivative Types:
thumbnail - Reduced resolution image/video previewtranscript - Speech-to-text for audio/videopreview - PDF or text representationocr - Extracted text from imagesCall the evidence.ingest tool:
{
"filePath": "/path/to/staged/file",
"filename": "original-filename.jpg",
"caseId": "case-2026-001",
"channel": "whatsapp",
"sender": "[email protected]",
"messageId": "msg-abc123",
"retentionDays": 2555,
"metadata": {
"exif": { ... },
"extracted": { ... }
}
}
Response:
{
"success": true,
"evidenceId": "ev-abc123...",
"sha256": "a1b2c3...",
"vaultUrl": "file:///vault/cases/case-2026-001/originals/ev-abc123.jpg",
"timestamp": "2026-02-17T10:30:00.000Z"
}
The manifest is automatically updated by the ingest operation.
Manifest Location: {vault}/cases/{caseId}/manifest.json
Manifest Contents:
Provide user with evidence receipt:
📋 EVIDENCE RECEIPT
Case ID: case-2026-001
Evidence ID: ev-abc123...
File: original-filename.jpg
SHA-256: a1b2c3d4e5f6...
Size: 1.2 MB
Received: 2026-02-17 10:30:00 UTC
Vault: file:///vault/cases/case-2026-001/originals/ev-abc123.jpg
✅ Chain of custody established
✅ Original preserved immutably
✅ Audit trail active
Ingest a file into the evidence vault.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| filePath | string | Yes | Path to the staged file |
| filename | string | Yes | Original filename |
| caseId | string | Yes | Case identifier |
| channel | string | No | Source channel |
| sender | string | No | Sender identifier |
| messageId | string | No | Message ID from source |
| retentionDays | number | No | Retention period |
| metadata | object | No | Additional metadata |
Returns: { evidenceId, sha256, vaultUrl, timestamp }
Verify evidence integrity.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| evidenceId | string | Yes | Evidence to verify |
| caseId | string | No | Limit search to case |
Returns: { verified, details: { originalIntact, hashMatch, lastVerifiedAt } }
Get case manifest.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| caseId | string | Yes | Case identifier |
Returns: Complete case manifest with all items
Export case as archive.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| caseId | string | Yes | Case identifier |
| format | string | No | 'zip' or 'tar' (default: zip) |
Returns: { exportPath, sha256, size, itemCount }
Get audit trail.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| caseId | string | Yes | Case identifier |
| limit | number | No | Max events (default: 100) |
Returns: { events: [ ... ], count }
Configure which channels can submit evidence:
# ~/.openclaw/openclaw.json
{
"plugins": {
"evidence-vault": {
"channelAllowlist": ["whatsapp", "telegram", "email"],
"channelDenylist": ["public-discord"],
"requirePairing": true
}
}
}
For Direct Messages (DMs):
For Group Channels:
ALL paths are validated:
../ sequences rejected\0 rejected~/ rejectedViolations result in: E_PATH_TRAVERSAL error + audit log entry
Before any potentially destructive operation:
Confirmation format:
⚠️ DESTRUCTIVE ACTION
You are about to: [describe action]
Case: [caseId]
Items affected: [count]
Type "CONFIRM" to proceed:
NEVER include in logs or manifests:
Redaction is automatic via regex patterns.
Cause: Filename or path contains disallowed characters
Solution:
Cause: File type not in allowed list
Solution:
allowedMimeTypes configurationCause: File exceeds maximum size
Solution:
maxFileSizeBytes configurationCause: Evidence from blocked or non-allowlisted channel
Solution:
Cause: Evidence file modified after ingest
Solution:
Symptoms: verified: false in verify response
Diagnostic steps:
# openclaw.yaml