Unicleaner provides a minimal Docker image built with a statically-linked musl binary for maximum portability and security.
docker pull ghcr.io/poelzi/unicleaner:latest# Scan current directory
docker run --rm -v "$(pwd):/workspace" ghcr.io/poelzi/unicleaner:latest .
# With specific options
docker run --rm -v "$(pwd):/workspace" ghcr.io/poelzi/unicleaner:latest \
scan . --format json > results.json# Mount config file
docker run --rm \
-v "$(pwd):/workspace" \
-v "$(pwd)/unicleaner.toml:/config/unicleaner.toml:ro" \
ghcr.io/poelzi/unicleaner:latest \
scan . --config /config/unicleaner.toml# Build static binary
nix build .#unicleaner-static
# Verify it's truly static
./scripts/verify-static.sh result/bin/unicleaner
# Build Docker image
nix build .#docker
# Load into Docker
docker load < result
# Tag and use
docker tag unicleaner:1.0.0-alpha1 unicleaner:latest
docker run --rm -v "$(pwd):/workspace" unicleaner:latest --version# Example Dockerfile using the static binary
FROM scratch
COPY result/bin/unicleaner /bin/unicleaner
WORKDIR /workspace
ENTRYPOINT ["/bin/unicleaner"]
CMD ["--help"]Build:
nix build .#unicleaner-static
docker build -t unicleaner:custom .The Docker image is extremely minimal:
- Base:
scratch(empty base image) - Binary: ~5-10 MB (statically linked musl binary)
- Total: ~5-10 MB uncompressed
- ✅ No OS packages - eliminates supply chain vulnerabilities
- ✅ No shell - prevents shell injection attacks
- ✅ Statically linked - no dynamic library dependencies
- ✅ Minimal attack surface - only contains the binary
Check that the binary is truly static:
# Extract binary from image
docker create --name temp unicleaner:latest
docker cp temp:/bin/unicleaner ./unicleaner-extracted
docker rm temp
# Verify no dynamic dependencies
ldd ./unicleaner-extracted
# Should output: "not a dynamic executable"
# Check binary info
file ./unicleaner-extracted
# Should show: "statically linked"name: Scan Code
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan for malicious Unicode
run: |
docker run --rm -v "$PWD:/workspace" \
ghcr.io/poelzi/unicleaner:latest \
scan . --format json > scan-results.json
- name: Check results
run: |
if [ -s scan-results.json ]; then
VIOLATIONS=$(jq '.violations | length' scan-results.json)
if [ "$VIOLATIONS" -gt 0 ]; then
echo "Found $VIOLATIONS violations!"
exit 1
fi
fiCreate .github/actions/unicleaner-scan/action.yml:
name: 'Unicleaner Scan'
description: 'Scan repository for malicious Unicode characters'
inputs:
path:
description: 'Path to scan'
required: false
default: '.'
config:
description: 'Path to config file'
required: false
fail-on-violations:
description: 'Fail if violations found'
required: false
default: 'true'
runs:
using: 'composite'
steps:
- name: Pull Unicleaner Docker image
shell: bash
run: docker pull ghcr.io/poelzi/unicleaner:latest
- name: Run scan
shell: bash
run: |
CONFIG_ARG=""
if [ -n "${{ inputs.config }}" ]; then
CONFIG_ARG="--config ${{ inputs.config }}"
fi
docker run --rm -v "$PWD:/workspace" \
ghcr.io/poelzi/unicleaner:latest \
scan ${{ inputs.path }} $CONFIG_ARG \
--format json > scan-results.json
- name: Check violations
shell: bash
if: inputs.fail-on-violations == 'true'
run: |
VIOLATIONS=$(jq '.violations | length' scan-results.json)
if [ "$VIOLATIONS" -gt 0 ]; then
echo "::error::Found $VIOLATIONS Unicode violations"
exit 1
fiUse it in your workflow:
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/unicleaner-scan
with:
path: src/
fail-on-violations: trueunicode-scan:
stage: security
image: docker:latest
services:
- docker:dind
script:
- docker pull ghcr.io/poelzi/unicleaner:latest
- docker run --rm -v "$PWD:/workspace"
ghcr.io/poelzi/unicleaner:latest
scan . --format json > results.json
- |
VIOLATIONS=$(jq '.violations | length' results.json)
if [ "$VIOLATIONS" -gt 0 ]; then
echo "Found $VIOLATIONS violations"
exit 1
fi
artifacts:
reports:
junit: results.json
when: alwaysversion: 2.1
jobs:
unicode-scan:
docker:
- image: cimg/base:current
steps:
- checkout
- setup_remote_docker
- run:
name: Pull Unicleaner
command: docker pull ghcr.io/poelzi/unicleaner:latest
- run:
name: Scan for malicious Unicode
command: |
docker run --rm -v "$PWD:/workspace" \
ghcr.io/poelzi/unicleaner:latest \
scan . --format json > results.json
- store_artifacts:
path: results.json
workflows:
version: 2
security:
jobs:
- unicode-scanpipeline {
agent any
stages {
stage('Unicode Security Scan') {
steps {
script {
docker.image('ghcr.io/poelzi/unicleaner:latest').inside('-v ${WORKSPACE}:/workspace') {
sh 'unicleaner scan . --format json > /workspace/results.json'
}
def results = readJSON file: 'results.json'
if (results.violations.size() > 0) {
error("Found ${results.violations.size()} Unicode violations")
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'results.json', allowEmptyArchive: true
}
}
}# Stage 1: Quick scan of critical files
docker run --rm -v "$(pwd):/workspace" \
ghcr.io/poelzi/unicleaner:latest \
scan src/ --severity error
# Stage 2: Full scan with warnings
docker run --rm -v "$(pwd):/workspace" \
ghcr.io/poelzi/unicleaner:latest \
scan . --severity warning --format json# In CI, scan only changed files
docker run --rm -v "$(pwd):/workspace" \
-v "$(pwd)/.git:/workspace/.git:ro" \
ghcr.io/poelzi/unicleaner:latest \
scan . --diff# Run with shell for debugging (requires adding shell to image)
docker run --rm -it --entrypoint /bin/sh \
-v "$(pwd):/workspace" \
unicleaner:latest
# Or use busybox base for debugging
FROM busybox
COPY --from=builder /bin/unicleaner /bin/unicleaner
WORKDIR /workspace
ENTRYPOINT ["/bin/unicleaner"]# Run as current user
docker run --rm --user $(id -u):$(id -g) \
-v "$(pwd):/workspace" \
ghcr.io/poelzi/unicleaner:latest .# Use absolute paths
docker run --rm -v "/absolute/path/to/code:/workspace" \
ghcr.io/poelzi/unicleaner:latest .
# Check mount worked
docker run --rm -v "$(pwd):/workspace" \
--entrypoint ls \
ghcr.io/poelzi/unicleaner:latest \
-la /workspaceIf you get "not found" or "no such file" errors:
# Check architecture
docker run --rm ghcr.io/poelzi/unicleaner:latest --version
# Verify static linking
docker run --rm --entrypoint sh ghcr.io/poelzi/unicleaner:latest \
-c "ldd /bin/unicleaner || echo 'Static binary confirmed'"