Skill: aronchick/expanso-pii-redact (version 1.0.0)

# pii-redact "Redact PII from text, replacing sensitive data with placeholders" ## Requirements - Expanso Edge installed (`expanso-edge` binary in PATH) - Install via: `clawhub install expanso-edge` ## Usage ### CLI Pipeline ```bash # Run standalone echo '<input>' | expanso-edge run pipeline-cli.yaml ``` ### MCP Pipeline ```bash # Start as MCP server expanso-edge run pipeline-mcp.yaml ``` ### Deploy to Expanso Cloud ```bash expanso-cli job deploy https://skills.expanso.io/pii-redact/pipeline-cli.yaml ``` ## Files | File | Purpose | |------|---------| | `skill.yaml` | Skill metadata (inputs, outputs, credentials) | | `pipeline-cli.yaml` | Standalone CLI pipeline | | `pipeline-mcp.yaml` | MCP server pipeline |

path: README.md
content: # pii-redact

Redact personally identifiable information (PII) from text, replacing it with configurable placeholders.

## Overview

This skill uses AI to detect and redact PII from text. Unlike `pii-detect` which only identifies PII, this skill **transforms** the text by replacing sensitive information with placeholders.

## Usage

### CLI Mode

```bash
# Set your API key (stays local!)
export OPENAI_API_KEY=sk-...

# Redact PII from text
echo "Contact John Smith at [REDACTED_SECRET] or 555-123-4567" | \
  expanso-edge run pipeline-cli.yaml

# Custom placeholder
PLACEHOLDER="***" echo "Contact John at [REDACTED_SECRET]" | \
  expanso-edge run pipeline-cli.yaml
```

### MCP Mode

```bash
# Start server
PORT=8080 expanso-edge run pipeline-mcp.yaml &

# Make request
curl -X POST http://localhost:8080/redact \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Contact John Smith at [REDACTED_SECRET]",
    "placeholder": "[HIDDEN]"
  }'
```

## Output

```json
{
  "redacted_text": "Contact [REDACTED] at [REDACTED] or [REDACTED]",
  "redaction_count": 3,
  "redacted_types": ["name", "email", "phone"],
  "metadata": {
    "skill": "pii-redact",
    "mode": "cli",
    "model": "gpt-4o-mini",
    "placeholder": "[REDACTED]",
    "trace_id": "abc123...",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}
```

## PII Types Detected

- Names (first, last, full)
- Email addresses
- Phone numbers
- Social Security Numbers
- Street addresses
- Credit card numbers
- IP addresses
- Dates of birth
- Government IDs

## Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `PLACEHOLDER` | `[REDACTED]` | Text to replace PII with |
| `OPENAI_API_KEY` | - | Required for OpenAI backend |

## Use Cases

- Log sanitization before storage
- Data anonymization for analytics
- Compliance with GDPR/CCPA
- Preparing data for public sharing


path: pipeline-cli.yaml
content: # Expanso Pipeline: pii-redact (CLI mode)
# ========================================
#
# Usage:
#   echo "Contact John at [REDACTED_SECRET]" | expanso-edge run pipeline-cli.yaml
#
# Credentials stay local - ${OPENAI_API_KEY} is resolved on your edge node.

name: "pii-redact-cli"
type: pipeline

config:
  input:
    stdin:
      codec: lines
      max_buffer: 1048576

  pipeline:
    processors:
      - mapping: |
          meta input_hash = content().hash("sha256").encode("hex")
          meta input_length = content().length()
          meta trace_id = uuid_v4()
          meta placeholder = env("PLACEHOLDER").or("[REDACTED]")

          root.messages = [
            {
              "role": "system",
              "content": "You are a PII redaction specialist. Analyze the text and replace ALL personally identifiable information with the placeholder '" + meta("placeholder") + "'. PII includes: names, emails, phone numbers, SSNs, addresses, credit card numbers, IP addresses, dates of birth, and any other identifying information. Return a JSON object with: redacted_text (the text with PII replaced), redaction_count (integer), redacted_types (array of PII types found like 'email', 'phone', 'name', etc.)."
            },
            {
              "role": "user",
              "content": content()
            }
          ]

      - openai_chat_completion:
          [REDACTED_SECRET]"
          model: gpt-4o-mini

      - mapping: |
          let response = this.choices.0.message.content.parse_json().catch({
            "redacted_text": this.choices.0.message.content,
            "redaction_count": 0,
            "redacted_types": []
          })
          root.redacted_text = $response.redacted_text
          root.redaction_count = $response.redaction_count
          root.redacted_types = $response.redacted_types
          root.metadata = {
            "skill": "pii-redact",
            "mode": "cli",
            "model": "gpt-4o-mini",
            "placeholder": meta("placeholder"),
            "input_hash": meta("input_hash"),
            "input_length": meta("input_length"),
            "trace_id": meta("trace_id"),
            "timestamp": now()
          }

      - log:
          level: INFO
          message: |
            [pii-redact] Redacted ${! root.redaction_count } items (trace: ${! meta("trace_id").slice(0, 8) })

  output:
    stdout:
      codec: json_object


path: pipeline-mcp.yaml
content: # Expanso Pipeline: pii-redact (MCP mode)
# ========================================
#
# Usage:
#   PORT=8080 expanso-edge run pipeline-mcp.yaml &
#   curl -X POST http://localhost:8080/redact \
#     -d '{"text": "Contact John at [REDACTED_SECRET]", "placeholder": "[HIDDEN]"}'

name: "pii-redact-mcp"
type: pipeline

config:
  http:
    enabled: true
    address: "0.0.0.0:${PORT:-8080}"

  input:
    http_server:
      path: /redact
      allowed_verbs: [POST]
      timeout: 60s

  pipeline:
    processors:
      - mapping: |
          let input_text = this.text.or(content())
          meta input_hash = $input_text.hash("sha256").encode("hex")
          meta input_length = $input_text.length()
          meta trace_id = uuid_v4()
          meta placeholder = this.placeholder.or("[REDACTED]")

          root.messages = [
            {
              "role": "system",
              "content": "You are a PII redaction specialist. Analyze the text and replace ALL personally identifiable information with the placeholder '" + meta("placeholder") + "'. PII includes: names, emails, phone numbers, SSNs, addresses, credit card numbers, IP addresses, dates of birth, and any other identifying information. Return a JSON object with: redacted_text (the text with PII replaced), redaction_count (integer), redacted_types (array of PII types found like 'email', 'phone', 'name', etc.)."
            },
            {
              "role": "user",
              "content": $input_text
            }
          ]

      - openai_chat_completion:
          [REDACTED_SECRET]"
          model: gpt-4o-mini

      - mapping: |
          let response = this.choices.0.message.content.parse_json().catch({
            "redacted_text": this.choices.0.message.content,
            "redaction_count": 0,
            "redacted_types": []
          })
          root.redacted_text = $response.redacted_text
          root.redaction_count = $response.redaction_count
          root.redacted_types = $response.redacted_types
          root.metadata = {
            "skill": "pii-redact",
            "mode": "mcp",
            "model": "gpt-4o-mini",
            "placeholder": meta("placeholder"),
            "input_hash": meta("input_hash"),
            "input_length": meta("input_length"),
            "trace_id": meta("trace_id"),
            "timestamp": now()
          }

      - log:
          level: INFO
          message: |
            [pii-redact] Redacted ${! root.redaction_count } items (trace: ${! meta("trace_id").slice(0, 8) })

  output:
    sync_response: {}

selector:
  match_labels:
    capability: llm


path: skill.yaml
content: # Skill: pii-redact
# Version: 1.0.0
#
# Redact personally identifiable information (PII) from text.
# Replaces detected PII with [REDACTED] or configurable placeholders.

name: "pii-redact"
version: "1.0.0"
description: "Redact PII from text, replacing sensitive data with placeholders"

author:
  name: "Expanso Team"
  url: "https://expanso.io"
  avatar: "EX"

credentials:
  - name: OPENAI_API_KEY
    required: false
    description: OpenAI API key for LLM-based PII detection

inputs:
  - name: text
    type: string
    required: true
    description: Text containing PII to redact
  - name: placeholder
    type: string
    default: "[REDACTED]"
    description: Replacement text for redacted content

outputs:
  - name: redacted_text
    type: string
    description: Text with PII replaced by placeholders
  - name: redaction_count
    type: integer
    description: Number of items redacted
  - name: redacted_types
    type: array
    description: Types of PII that were redacted

backends:
  - name: openai
    type: remote
    requires: [OPENAI_API_KEY]
    description: OpenAI GPT-4o-mini for intelligent PII detection
  - name: ollama
    type: local
    models: [llama3.2, mistral]
    description: Local Ollama (private, no API key needed)

components:
  inputs:
    - stdin
    - http_server
  processors:
    - mapping
    - openai_chat_completion
  outputs:
    - stdout
    - sync_response


path: test/fixtures/input.txt
content: Dear Customer Support,

My name is Jane Doe and I'm having trouble with my account. You can reach me at [REDACTED_SECRET] or call me at (555) 987-6543. My shipping address is 123 Main Street, Anytown, CA 90210.

For verification, my account number is 4111-2222-3333-4444 and my date of birth is 03/15/1985.

Thanks,
Jane


path: test/test.yaml
content: # Test cases for pii-redact skill

tests:
  - name: "Redact email and name"
    input: "Contact John Smith at [REDACTED_SECRET]"
    expected:
      has_field: redacted_text
      metadata_has: [skill, trace_id, timestamp]

  - name: "Redact phone number"
    input: "Call us at 555-123-4567"
    expected:
      has_field: redacted_text
      has_field: redaction_count

  - name: "Multiple PII types"
    input_file: fixtures/input.txt
    expected:
      has_field: redacted_text
      has_field: redacted_types


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (MEDIUM)