Skill: nickconstantinou/publish (version 1.0.12)

--- name: clawback description: Backup and restore your OpenClaw workspace to GitHub metadata: { "openclaw": { "emoji": "💾", "requires": { "env": ["GITHUB_TOKEN", "BACKUP_REPO", "OPENCLAW_WORKSPACE"] } } } --- # ClawSync Backup and restore your OpenClaw workspace to GitHub. ## ⚠️ Security First This skill is designed with defense-in-depth. Please read carefully. ## What It Backs Up | Category | Files | Status | |----------|-------|--------| | **Skills** | All from `$OPENCLAW/skills/` | See notes below | | **Scripts** | All from `$OPENCLAW/scripts/` | See notes below | | **Project Code** | All from `$OPENCLAW/workspace/projects/` | Excluding credentials | ### What It Does NOT Back Up (Personal/Workspace-Specific) These files are **explicitly excluded** as they are personal or workspace-specific: - **AGENTS.md, SOUL.md, USER.md, TOOLS.md, IDENTITY.md, HEARTBEAT.md** — Personal agent configuration - **SITES.md** — May contain API keys/secrets - **MEMORY.md** — Contains sensitive conversation data - Any file in `credentials/`, `.env`, `node_modules/` ## What It Excludes - ❌ API keys and tokens (any format) - ❌ Credentials folder - ❌ .env files - ❌ node_modules - ❌ .git directories - ❌ Nested git repositories - ❌ Files containing secrets (detected by regex) ## Secret Detection ClawSync scans for these secret patterns: - GitHub tokens (`ghp_*`) - OpenAI keys (`sk-*`) - Google API keys (`AIza*`) - Slack tokens (`xoxb-*`, `xoxp-*`) - AWS access keys (`AKIA*`) - JWTs and bearer tokens - Private keys (`-----BEGIN * PRIVATE KEY-----`) - High-entropy strings If any are detected → **backup aborts before push**. ## Environment Variables (Required) ```bash export GITHUB_TOKEN="ghp_xxxx" export BACKUP_REPO="username/repo-name" export OPENCLAW_WORKSPACE="${HOME}/openclaw-workspace" ``` ### 🔐 Recommended: Fine-Grained PAT For least privilege, use a GitHub Fine-Grained PAT: 1. Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens 2. Create new token with: - **Repository access**: Only `$BACKUP_REPO` - **Permissions**: Contents: Write 3. Use this token as `GITHUB_TOKEN` ## Quick Start ```bash git clone https://github.com/your-username/clawsync.git ~/clawsync cp .env.example .env # Edit .env with your values bash sync.sh ``` ## Features - **Pre-flight Check**: Validates required env vars before running - **Strict Whitelist**: Only copies explicitly allowed files - **Deny List**: Filters out .git, credentials, node_modules - **Secret Scrubbing**: Detects 100+ secret patterns, aborts if found - **Safe Restore**: Requires --force or confirmation before overwriting ## Safe Restore ```bash # With confirmation (default) bash restore.sh # Force mode (no prompt) bash restore.sh --force ``` ## Auth Uses gh CLI if available, falls back to token auth. ## Files - `sync.sh` - Backup script (ShellCheck compliant) - `restore.sh` - Restore script - `.env_example` - Template - `.gitignore` - Blocks secrets ## Development & Release ### Running Tests Locally ```bash # Set up test workspace mkdir -p /tmp/test-workspace echo "test" > /tmp/test-workspace/AGENTS.md echo "test" > /tmp/test-workspace/USER.md mkdir -p /tmp/test-workspace/skills /tmp/test-workspace/scripts # Run integration test export BACKUP_REPO="test/repo" export OPENCLAW_WORKSPACE="/tmp/test-workspace" export GITHUB_TOKEN="dummy" cd /tmp && rm -rf test-backup-repo && mkdir test-backup-repo cd test-backup-repo && git init cp ~/clawsync/sync.sh . bash sync.sh ``` ### Testing Secret Detection ```bash # Create a test file with a fake secret echo "My API key is [REDACTED_SECRET]" > /tmp/test-workspace/AGENTS.md # Run sync - should abort with error bash sync.sh # Expected output: "Error: Potential secret detected..." ``` ### Security Audit Test (Proves Non-Staged Detection) This test verifies the script catches secrets BEFORE they are staged: ```bash # Set up test workspace export BACKUP_REPO="test/repo" export OPENCLAW_WORKSPACE="/tmp/test-workspace" export GITHUB_TOKEN="dummy" # Create workspace with secret in a non-staged file mkdir -p /tmp/test-workspace echo "Real API key: [REDACTED_SECRET]" > /tmp/test-workspace/AGENTS.md # Copy sync.sh to temp backup dir cd /tmp && rm -rf audit-test && mkdir audit-test && cd audit-test git init cp ~/clawsync/sync.sh . # Run sync - should FAIL (catches non-staged secret) bash sync.sh # Expected: "Error: Potential secret detected in backup directory!" # This proves the pre-git-add scanning works ``` ### Publishing to ClawHub The CI runs on every push and pull request: 1. **ShellCheck** - Lints bash scripts 2. **Integration test** - Verifies backup/restore works To publish a new version: ```bash git add -A git commit -m "Release v1.0.x" git tag v1.0.x git push origin master --tags ``` CI will automatically: - Run tests - If tests pass and tag starts with `v*`, publish to ClawHub

path: README.md
content: # ClawSync

Backup and restore your OpenClaw workspace to GitHub.

## ⚠️ Important: Do NOT Commit Personal Files

This repo is published to ClawHub. **Do NOT commit** these files:
- `AGENTS.md`, `SOUL.md`, `USER.md`, `TOOLS.md`, `IDENTITY.md`, `HEARTBEAT.md`
- `SITES.md` (contains API keys)
- `MEMORY.md` (sensitive conversation data)

These files are in `.gitignore` for a reason. Personal/workspace-specific configuration should stay on your local machine.

## Quick Start

```bash
git clone https://github.com/your-username/ClawSync.git ~/ClawSync
cp .env.example .env
# Edit .env with your values
bash sync.sh
```

## Auth

Uses gh CLI if available, falls back to token auth.

## Files

- `sync.sh` - Backup
- `restore.sh` - Restore
- `.env.example` - Template


path: restore.sh
content: #!/bin/bash
# ClawSync - Secure Restore Script
# ShellCheck compliant

set -euo pipefail

# === Pre-flight Check ===
REQUIRED_VARS=("BACKUP_REPO" "OPENCLAW_WORKSPACE")
for var in "${REQUIRED_VARS[@]}"; do
    if [[ -z "${!var}" ]]; then
        echo "Error: $var is not set. Please configure in .env"
        exit 1
    fi
done

WORKSPACE="${OPENCLAW_WORKSPACE}"
BACKUP_REPO="${BACKUP_REPO}"
BRANCH="${BACKUP_BRANCH:-main}"
[REDACTED_SECRET]"
FORCE="${1:-}"

BACKUP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
mkdir -p "$WORKSPACE"

echo "=== ClawSync Restore ==="

# === Safe Restore: Require --force or confirmation ===
if [[ "$FORCE" != "--force" ]] && [[ "$FORCE" != "-f" ]]; then
    echo "Warning: This will overwrite existing files in $WORKSPACE"
    echo "Are you sure? (y/n)"
    read -r response
    if [[ "$response" != "y" ]] && [[ "$response" != "Y" ]]; then
        echo "Restore cancelled."
        exit 0
    fi
fi

# === Auth & Clone (Secure) ===
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT

if gh auth status &>/dev/null; then
    gh auth setup-git 2>/dev/null || true
    gh repo clone "$BACKUP_REPO" "$TEMP_DIR" -- -b "$BRANCH" 2>/dev/null || \
        git clone -b "$BRANCH" "https://github.com/${BACKUP_REPO}.git" "$TEMP_DIR"
else
    if [[ -n "$TOKEN" ]]; then
        # Use credential helper instead of command-line token
        printf "https://%[REDACTED_SECRET]" "$TOKEN" | git credential approve
        git clone -b "$BRANCH" "https://github.com/${BACKUP_REPO}.git" "$TEMP_DIR"
        printf "https://%[REDACTED_SECRET]" "$TOKEN" | git credential reject
    else
        echo "Error: No GH auth or GITHUB_TOKEN"
        exit 1
    fi
fi

# === Restore Files (no-clobber by default) ===
EXCLUDE_FILES=("SITES.md" "MEMORY.md" ".env" "credentials")

should_restore() {
    local file="$1"
    for excl in "${EXCLUDE_FILES[@]}"; do
        if [[ "$file" == "$excl" ]]; then
            return 1
        fi
    done
    return 0
}

# Restore skills
if [[ -d "$TEMP_DIR/skills" ]]; then
    mkdir -p "$WORKSPACE/skills"
    for item in "$TEMP_DIR/skills"/*; do
        if [[ -d "$item" ]] && should_restore "$(basename "$item")"; then
            cp -rn "$item" "$WORKSPACE/skills/" 2>/dev/null || true
        fi
    done
fi

# Restore scripts
if [[ -d "$TEMP_DIR/scripts" ]]; then
    mkdir -p "$WORKSPACE/scripts"
    for item in "$TEMP_DIR/scripts"/*; do
        if [[ -f "$item" ]] && should_restore "$(basename "$item")"; then
            cp -rn "$item" "$WORKSPACE/scripts/" 2>/dev/null || true
        fi
    done
fi

# Restore identity files (exclude sensitive ones)
for file in AGENTS.md SOUL.md USER.md TOOLS.md IDENTITY.md HEARTBEAT.md; do
    if [[ -f "$TEMP_DIR/$file" ]]; then
        cp -n "$TEMP_DIR/$file" "$WORKSPACE/" 2>/dev/null || true
    fi
done

echo "Restore complete!"


path: sync.sh
content: #!/bin/bash
# ClawSync - Secure Backup Script
# ShellCheck compliant: https://www.shellcheck.net/

set -euo pipefail

# === Pre-flight Check ===
REQUIRED_VARS=("BACKUP_REPO" "OPENCLAW_WORKSPACE")
for var in "${REQUIRED_VARS[@]}"; do
    if [[ -z "${!var}" ]]; then
        echo "Error: $var is not set. Please configure in .env"
        exit 1
    fi
done

WORKSPACE="${OPENCLAW_WORKSPACE}"
BACKUP_REPO="${BACKUP_REPO}"
BRANCH="${BACKUP_BRANCH:-main}"
[REDACTED_SECRET]"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="${SCRIPT_DIR}"

echo "=== ClawSync Backup ==="

# === Explicit Exclusions (Documented) ===
# These files are NEVER backed up - see rsync --exclude flags below for actual enforcement

# === Safe Copy: Skills using rsync with exclude patterns ===
if [[ -d "$WORKSPACE/skills" ]] && [[ -n "$(ls -A "$WORKSPACE/skills/" 2>/dev/null)" ]]; then
    rsync -a \
        --exclude='.git' \
        --exclude='node_modules' \
        --exclude='AGENTS.md' \
        --exclude='SOUL.md' \
        --exclude='USER.md' \
        --exclude='TOOLS.md' \
        --exclude='IDENTITY.md' \
        --exclude='HEARTBEAT.md' \
        --exclude='SITES.md' \
        --exclude='MEMORY.md' \
        "$WORKSPACE/skills/" "$BACKUP_DIR/skills/" 2>/dev/null || {
        # Fallback to cp if rsync fails
        rm -rf "$BACKUP_DIR/skills"
        cp -r "$WORKSPACE/skills" "$BACKUP_DIR/"
    }
fi

# === Safe Copy: Scripts using rsync with exclude patterns ===
if [[ -d "$WORKSPACE/scripts" ]] && [[ -n "$(ls -A "$WORKSPACE/scripts/" 2>/dev/null)" ]]; then
    rsync -a \
        --exclude='.git' \
        --exclude='AGENTS.md' \
        --exclude='SOUL.md' \
        --exclude='USER.md' \
        --exclude='TOOLS.md' \
        --exclude='IDENTITY.md' \
        --exclude='HEARTBEAT.md' \
        --exclude='SITES.md' \
        --exclude='MEMORY.md' \
        "$WORKSPACE/scripts/" "$BACKUP_DIR/scripts/" 2>/dev/null || {
        # Fallback to cp if rsync fails
        rm -rf "$BACKUP_DIR/scripts"
        cp -r "$WORKSPACE/scripts" "$BACKUP_DIR/"
    }
fi

echo "Files copied"

cd "$BACKUP_DIR"
if [[ ! -d ".git" ]]; then
    git init
    git remote add origin "https://github.com/${BACKUP_REPO}.git"
fi

# === COMPREHENSIVE SECRET SCANNING ===
# Scan ENTIRE backup directory (not just staged files)
SECRET_PATTERN='(ghp_[a-zA-Z0-9]{36}|sk-[a-zA-Z0-9]{20,}|AIza[a-zA-Z0-9_-]{35}|xox[pborsa]-[0-9a-zA-Z]{10,48}|AKIA[0-9A-Z]{16}|[a-zA-Z0-9/+=]{40,}|(-----BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY-----))'

# Recursively scan all files in backup directory
SECRETS_FOUND=$(grep -rE "$SECRET_PATTERN" "$BACKUP_DIR" --exclude-dir=.git --line-number 2>/dev/null || true)

if [[ -n "$SECRETS_FOUND" ]]; then
    echo "Error: Potential secret detected in backup directory!"
    echo "$SECRETS_FOUND"
    echo "Aborting backup for safety."
    rm -rf "$BACKUP_DIR/.git"
    exit 1
fi

# === Git Operations ===
git add -A

# Stage-level scan (additional check)
if git diff --cached --name-only | xargs -I {} grep -qE "$SECRET_PATTERN" {} 2>/dev/null; then
    echo "Error: Potential secret detected in staged files. Aborting."
    git diff --cached --name-only | xargs -I {} grep -lE "$SECRET_PATTERN" {} 2>/dev/null || true
    git reset
    exit 1
fi

# === SECURE Git Push (No Token in Process) ===
if gh auth status &>/dev/null; then
    # Use gh CLI - no token exposure
    gh auth setup-git 2>/dev/null || true
    if ! git diff --cached --quiet; then
        git commit -m "Backup: $(date '+%Y-%m-%d %H:%M')"
        gh repo sync . --branch "$BRANCH" 2>/dev/null || git push -u origin "$BRANCH"
        echo "Backup complete!"
    fi
else
    # Fallback: Use git credential helper (more secure than command-line)
    if [[ -n "$TOKEN" ]]; then
        # Store token in git credential helper (not visible in ps)
        printf "https://%[REDACTED_SECRET]" "$TOKEN" | git credential approve
        
        if ! git diff --cached --quiet; then
            git commit -m "Backup: $(date '+%Y-%m-%d %H:%M')"
            git push -u origin "$BRANCH"
            echo "Backup complete!"
        fi
        
        # Clear credential
        printf "https://%[REDACTED_SECRET]" "$TOKEN" | git credential reject
    else
        echo "Error: No GH auth or GITHUB_TOKEN"
        exit 1
    fi
fi


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