✓ Verified 💻 Development ✓ Enhanced Data

Skill Evidenceops

Forensic media triage with chain of custody.

Rating
4.6 (43 reviews)
Downloads
3,057 downloads
Version
1.0.0

Overview

Forensic media triage with chain of custody.

Key Features

1

Receive Media

2

Create or Select Case

3

Stage Original (Read-Only)

4

Extract Metadata

5

Generate Derivatives (Optional)

6

Ingest to Vault

7

Update Manifest

8

Return Receipt

Complete Documentation

View Source →

EvidenceOps - Forensic Media Triage

What It Does

EvidenceOps provides forensic-grade handling of media files with complete chain of custody:

  • Media Intake - Accept images, videos, audio, PDFs, and documents from any channel
  • Immutable Storage - Store originals in append-only vault with cryptographic hashes
  • Metadata Extraction - Extract EXIF, file properties, and media information without altering originals
  • Derivative Generation - Create thumbnails, transcripts, previews in separate folders
  • Chain of Custody - Maintain tamper-evident audit trail with hash chain
  • Integrity Verification - Verify evidence hasn't been modified post-ingest
  • Audit Trail - Complete JSONL audit log for compliance

What It NEVER Does

  • NEVER modifies original evidence files after ingest
  • NEVER stores secrets, API keys, or credentials in manifests or logs
  • NEVER accepts unsanitized paths from user input
  • NEVER executes untrusted code or downloads remote scripts
  • NEVER exfiltrates data to external services without explicit configuration
  • NEVER bypasses channel allowlists or pairing requirements
  • NEVER stores real personal data in example files

Prerequisites

Before using this skill, ensure:

  • Plugin @openclaw/evidence-vault is installed and initialized
  • Vault storage directory is configured with appropriate permissions
  • Channel allowlist is configured for trusted sources only
  • Retention policies comply with your legal requirements

Workflow

Step 1: Receive Media

When media is received via any channel:

text
User sends: [image/video/document]

Required Information:

  • File content (from attachment)
  • Original filename
  • Source channel (whatsapp, telegram, email, etc.)
  • Sender identifier
  • Message ID (if available)

Step 2: Create or Select Case

text
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}

  • Must match pattern: ^case-[a-zA-Z0-9_-]+$
  • Examples: case-2026-001, case-incident-alpha, case-legal-2026-q1

Step 3: Stage Original (Read-Only)

Before ingest:

  • Save received file to temporary staging area
  • Calculate SHA-256 hash immediately
  • Record file size and MIME type
  • DO NOT modify the file
text
# Staging directory structure
/tmp/evidence-staging/
├── {caseId}/
│   └── {timestamp}-{filename}

Step 4: Extract Metadata

Extract metadata WITHOUT modifying original:

For Images:

  • EXIF data (camera, GPS, timestamps)
  • Dimensions
  • Color profile
For Videos:
  • Duration
  • Codec information
  • Resolution
For Audio:
  • Duration
  • Sample rate
  • Codec
For PDFs:
  • Page count
  • Author (if embedded)
  • Creation date
text
# Use Read tool or appropriate extraction commands
# NEVER write back to original file

Step 5: Generate Derivatives (Optional)

Create derivative artifacts in SEPARATE folder:

text
derivatives/
├── thumbnails/
│   └── {evidenceId}-thumb.jpg
├── transcripts/
│   └── {evidenceId}-transcript.txt
└── previews/
    └── {evidenceId}-preview.pdf

Derivative Types:

  • thumbnail - Reduced resolution image/video preview
  • transcript - Speech-to-text for audio/video
  • preview - PDF or text representation
  • ocr - Extracted text from images

Step 6: Ingest to Vault

Call the evidence.ingest tool:

json
{
  "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:

json
{
  "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"
}

Step 7: Update Manifest

The manifest is automatically updated by the ingest operation.

Manifest Location: {vault}/cases/{caseId}/manifest.json

Manifest Contents:

  • Case metadata
  • Evidence items with hashes
  • Derivatives
  • Chain of custody entries
  • Retention policy

Step 8: Return Receipt

Provide user with evidence receipt:

text
📋 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

Tool Reference

evidence.ingest

Ingest a file into the evidence vault.

Parameters:

ParameterTypeRequiredDescription
filePathstringYesPath to the staged file
filenamestringYesOriginal filename
caseIdstringYesCase identifier
channelstringNoSource channel
senderstringNoSender identifier
messageIdstringNoMessage ID from source
retentionDaysnumberNoRetention period
metadataobjectNoAdditional metadata
Returns: { evidenceId, sha256, vaultUrl, timestamp }

evidence.verify

Verify evidence integrity.

Parameters:

ParameterTypeRequiredDescription
evidenceIdstringYesEvidence to verify
caseIdstringNoLimit search to case
Returns: { verified, details: { originalIntact, hashMatch, lastVerifiedAt } }

evidence.manifest

Get case manifest.

Parameters:

ParameterTypeRequiredDescription
caseIdstringYesCase identifier
Returns: Complete case manifest with all items

evidence.export

Export case as archive.

Parameters:

ParameterTypeRequiredDescription
caseIdstringYesCase identifier
formatstringNo'zip' or 'tar' (default: zip)
Returns: { exportPath, sha256, size, itemCount }

evidence.access_log

Get audit trail.

Parameters:

ParameterTypeRequiredDescription
caseIdstringYesCase identifier
limitnumberNoMax events (default: 100)
Returns: { events: [ ... ], count }

SECURITY POSTURE

Channel Allowlist Configuration

Configure which channels can submit evidence:

yaml
# ~/.openclaw/openclaw.json
{
  "plugins": {
    "evidence-vault": {
      "channelAllowlist": ["whatsapp", "telegram", "email"],
      "channelDenylist": ["public-discord"],
      "requirePairing": true
    }
  }
}

Pairing Requirements

For Direct Messages (DMs):

  • REQUIRE pairing before accepting evidence
  • BLOCK unpaired DMs by default
  • LOG rejected ingestion attempts
For Group Channels:
  • VERIFY channel is in allowlist
  • REJECT evidence from untrusted channels
  • NEVER auto-ingest from public channels

Path Sanitization Rules

ALL paths are validated:

  • No path traversal - ../ sequences rejected
  • No null bytes - \0 rejected
  • No home directory - ~/ rejected
  • Canonicalization - Paths resolved before validation
  • Base path check - Must be within vault directory
Violations result in: E_PATH_TRAVERSAL error + audit log entry

Destructive Action Confirmation

Before any potentially destructive operation:

  • EXPORT - Confirm export destination
  • RETENTION DELETE - Require explicit confirmation (if enabled)
  • LEGAL HOLD RELEASE - Require explicit confirmation
Confirmation format:
text
⚠️ DESTRUCTIVE ACTION

You are about to: [describe action]
Case: [caseId]
Items affected: [count]

Type "CONFIRM" to proceed:

Secrets Handling

NEVER include in logs or manifests:

  • API keys
  • Passwords
  • Tokens
  • Credit card numbers
  • Email addresses (redacted)
  • Phone numbers (redacted)
  • SSN (redacted)
Redaction is automatic via regex patterns.

Troubleshooting

Error: E_PATH_TRAVERSAL

Cause: Filename or path contains disallowed characters

Solution:

  • Filenames are automatically sanitized
  • If error persists, check for unusual characters
  • Original filename is preserved in manifest metadata

Error: E_INVALID_MIME

Cause: File type not in allowed list

Solution:

  • Check allowedMimeTypes configuration
  • Add MIME type to allowlist if appropriate
  • Verify file is not corrupted

Error: E_SIZE_LIMIT

Cause: File exceeds maximum size

Solution:

  • Check maxFileSizeBytes configuration
  • Split large files if appropriate
  • Compress before ingest (note in metadata)

Error: E_CHANNEL_BLOCKED

Cause: Evidence from blocked or non-allowlisted channel

Solution:

  • Verify channel in allowlist
  • Check if pairing is required
  • Review security configuration

Error: E_HASH_MISMATCH

Cause: Evidence file modified after ingest

Solution:

  • This indicates potential tampering
  • Review access logs for case
  • Escalate per incident response procedures

Verification Fails

Symptoms: verified: false in verify response

Diagnostic steps:

  • Check if original file exists
  • Compare current hash with manifest hash
  • Review audit log for modifications
  • Check filesystem permissions

Configuration Example

yaml
# openclaw.yaml
plugins:
  evidence-vault:
    driver: filesystem  # or s3
    basePath: /var/evidence-vault
    maxFileSizeBytes: 524288000  # 500MB
    defaultRetentionDays: 2555   # 7 years
    allowedMimeTypes:
      - image/jpeg
      - image/png
      - video/mp4
      - audio/mpeg
      - application/pdf
    channelAllowlist:
      - whatsapp
      - telegram
      - slack
    requirePairing: true
    
    # S3 driver options (if driver: s3)
    s3:
      endpoint: https://s3.example.com
      bucket: evidence-vault
      region: us-east-1
      objectLock: true

Legal and Compliance Notes

LGPD/GDPR Considerations

  • Tag sensitive data with sensitivityTag
  • Configure appropriate retention periods
  • Never store real personal data in examples
  • Implement data subject access request procedures

Retention Policies

SensitivityDefault Retention
public365 days
internal1825 days (5 years)
confidential2555 days (7 years)
restrictedLegal hold

Audit Requirements

All operations logged to JSONL audit file:

  • Case creation/deletion
  • Evidence ingest
  • Verification attempts
  • Export operations
  • Access requests
Audit log retention: Same as case retention

Installation

Terminal bash

openclaw install skill-evidenceops
    
Copied!

💻Code Examples

User sends: [image/video/document]

user-sends-imagevideodocument.txt
**Required Information:**
- File content (from attachment)
- Original filename
- Source channel (whatsapp, telegram, email, etc.)
- Sender identifier
- Message ID (if available)

### Step 2: Create or Select Case

ASK user: "Should I create a new case or add to existing case [case-2026-XXX]?"

-ask-user-should-i-create-a-new-case-or-add-to-existing-case-case-2026-xxx.txt
**Case ID Format:** `case-{year}-{sequence}`
- Must match pattern: `^case-[a-zA-Z0-9_-]+$`
- Examples: `case-2026-001`, `case-incident-alpha`, `case-legal-2026-q1`

### Step 3: Stage Original (Read-Only)

Before ingest:

1. **Save received file to temporary staging area**
2. **Calculate SHA-256 hash immediately**
3. **Record file size and MIME type**
4. **DO NOT modify the file**

│ └── {timestamp}-{filename}

--timestamp-filename.txt
### Step 4: Extract Metadata

Extract metadata WITHOUT modifying original:

**For Images:**
- EXIF data (camera, GPS, timestamps)
- Dimensions
- Color profile

**For Videos:**
- Duration
- Codec information
- Resolution

**For Audio:**
- Duration
- Sample rate
- Codec

**For PDFs:**
- Page count
- Author (if embedded)
- Creation date

# NEVER write back to original file

-never-write-back-to-original-file.txt
### Step 5: Generate Derivatives (Optional)

Create derivative artifacts in SEPARATE folder:

└── {evidenceId}-preview.pdf

--evidenceid-previewpdf.txt
**Derivative Types:**
- `thumbnail` - Reduced resolution image/video preview
- `transcript` - Speech-to-text for audio/video
- `preview` - PDF or text representation
- `ocr` - Extracted text from images

### Step 6: Ingest to Vault

Call the evidence.ingest tool:

}

.txt
### Step 7: Update Manifest

The manifest is automatically updated by the ingest operation.

**Manifest Location:** `{vault}/cases/{caseId}/manifest.json`

**Manifest Contents:**
- Case metadata
- Evidence items with hashes
- Derivatives
- Chain of custody entries
- Retention policy

### Step 8: Return Receipt

Provide user with evidence receipt:

✅ Audit trail active

-audit-trail-active.txt
## Tool Reference

### evidence.ingest

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 }`

### evidence.verify

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 } }`

### evidence.manifest

Get case manifest.

**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| caseId | string | Yes | Case identifier |

**Returns:** Complete case manifest with all items

### evidence.export

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 }`

### evidence.access_log

Get audit trail.

**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| caseId | string | Yes | Case identifier |
| limit | number | No | Max events (default: 100) |

**Returns:** `{ events: [ ... ], count }`

## SECURITY POSTURE

### Channel Allowlist Configuration

Configure which channels can submit evidence:

}

.txt
### Pairing Requirements

For Direct Messages (DMs):
- **REQUIRE** pairing before accepting evidence
- **BLOCK** unpaired DMs by default
- **LOG** rejected ingestion attempts

For Group Channels:
- **VERIFY** channel is in allowlist
- **REJECT** evidence from untrusted channels
- **NEVER** auto-ingest from public channels

### Path Sanitization Rules

ALL paths are validated:

1. **No path traversal** - `../` sequences rejected
2. **No null bytes** - `\0` rejected
3. **No home directory** - `~/` rejected
4. **Canonicalization** - Paths resolved before validation
5. **Base path check** - Must be within vault directory

**Violations result in:** `E_PATH_TRAVERSAL` error + audit log entry

### Destructive Action Confirmation

Before any potentially destructive operation:

1. **EXPORT** - Confirm export destination
2. **RETENTION DELETE** - Require explicit confirmation (if enabled)
3. **LEGAL HOLD RELEASE** - Require explicit confirmation

**Confirmation format:**

Type "CONFIRM" to proceed:

type-confirm-to-proceed.txt
### Secrets Handling

**NEVER include in logs or manifests:**
- API keys
- Passwords
- Tokens
- Credit card numbers
- Email addresses (redacted)
- Phone numbers (redacted)
- SSN (redacted)

**Redaction is automatic** via regex patterns.

## Troubleshooting

### Error: E_PATH_TRAVERSAL

**Cause:** Filename or path contains disallowed characters

**Solution:**
- Filenames are automatically sanitized
- If error persists, check for unusual characters
- Original filename is preserved in manifest metadata

### Error: E_INVALID_MIME

**Cause:** File type not in allowed list

**Solution:**
- Check `allowedMimeTypes` configuration
- Add MIME type to allowlist if appropriate
- Verify file is not corrupted

### Error: E_SIZE_LIMIT

**Cause:** File exceeds maximum size

**Solution:**
- Check `maxFileSizeBytes` configuration
- Split large files if appropriate
- Compress before ingest (note in metadata)

### Error: E_CHANNEL_BLOCKED

**Cause:** Evidence from blocked or non-allowlisted channel

**Solution:**
- Verify channel in allowlist
- Check if pairing is required
- Review security configuration

### Error: E_HASH_MISMATCH

**Cause:** Evidence file modified after ingest

**Solution:**
- This indicates potential tampering
- Review access logs for case
- Escalate per incident response procedures

### Verification Fails

**Symptoms:** `verified: false` in verify response

**Diagnostic steps:**
1. Check if original file exists
2. Compare current hash with manifest hash
3. Review audit log for modifications
4. Check filesystem permissions

## Configuration Example
example.txt
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]?"

Tags

#coding_agents-and-ides

Quick Info

Category Development
Model Claude 3.5
Complexity One-Click
Author msrovani
Last Updated 3/10/2026
🚀
Optimized for
Claude 3.5
🧠

Ready to Install?

Get started with this skill in seconds

openclaw install skill-evidenceops