Every photo from a phone camera ships with an EXIF block that leaks:
GPS coordinates, camera model + serial, original timestamp, software
name, author/copyright fields, sometimes an embedded thumbnail that
survives cropping. For a social feed positioned as privacy-friendly
we can't trust the client alone to scrub — a compromised build,
a future plugin, or a hostile fork would simply skip the step and
leak authorship data.
So: server-side scrub is mandatory for every /feed/publish upload.
New package: media
media/scrub.go
- Scrubber type with Scrub(ctx, bytes, claimedMIME) → (clean, actualMIME)
- ScrubImage handles JPEG/PNG/GIF/WebP in-process: decodes, optionally
downscales to 1080px max-dim, re-encodes as JPEG Q=75. Stdlib
jpeg.Encode emits ZERO metadata → scrub is complete by construction.
- Sidecar client (HTTP): posts video/audio bytes to an external
FFmpeg worker at DCHAIN_MEDIA_SIDECAR_URL
- Magic-byte MIME detection: rejects uploads where declared MIME
doesn't match actual bytes (prevents a PDF dressed as image/jpeg
from bypassing the scrubber)
- ErrSidecarUnavailable: explicit error when video arrives but no
sidecar is wired; operator opts in to fallback via
--allow-unscrubbed-video (default: reject)
media/scrub_test.go
- Crafted EXIF segment with "SECRETGPS-…Canon-EOS-R5" canary —
verifies the string is gone after ScrubImage
- Downscale test (2000×1000 → 1080×540, aspect preserved)
- MIME-mismatch rejection
- Magic-byte detector sanity table
FFmpeg sidecar — new docker/media-sidecar/
Tiny Go HTTP service (~180 LOC, no non-stdlib deps) that shells out
to ffmpeg with -map_metadata -1 + -map 0:v -map 0:a? to guarantee
only video + audio streams survive (no subtitles, attached pictures,
or data channels that could carry hidden info).
Re-encode profile:
video → H.264 CRF 28 preset=fast, Opus 64k, MP4 faststart
audio → Opus 64k, Ogg container
Dockerfile: two-stage build (Go → alpine+ffmpeg), ~90 MB image, non-
root user, /healthz endpoint for compose probes.
Node reaches it via DCHAIN_MEDIA_SIDECAR_URL. Without it, video uploads
are rejected with 503 unless operator sets DCHAIN_ALLOW_UNSCRUBBED_VIDEO.
/feed/publish wiring
- cfg.Scrubber is a required dependency
- Before storing post body we call scrubber.Scrub(); attachment bytes
+ MIME are replaced with the cleaned version
- content_hash is computed over the SCRUBBED bytes — so the on-chain
CREATE_POST tx references exactly what readers will fetch
- EstimatedFeeUT uses the scrubbed size, so author's fee reflects
actual on-disk cost
- Content-type mismatches → 400; sidecar unavailable for video → 503
Flags / env vars
--feed-db / DCHAIN_FEED_DB (existing)
--feed-ttl-days / DCHAIN_FEED_TTL_DAYS (existing)
--media-sidecar-url / DCHAIN_MEDIA_SIDECAR_URL (NEW)
--allow-unscrubbed-video / DCHAIN_ALLOW_UNSCRUBBED_VIDEO (NEW; default false)
Client responsibilities (for reference — client work lands in Phase C)
Even with server-side scrub, the client should still compress aggressively
BEFORE upload, because:
- upload time is ~N× larger for unscrubbed media (mobile networks)
- the server's 256 KiB MaxPostSize is a HARD cap — oversized uploads
are rejected, not silently truncated
- the on-chain fee is size-based, so users pay for every byte the
client didn't bother to shrink
Recommended client pipeline:
images → expo-image-manipulator: resize max-dim 1080px, WebP or
JPEG quality 50-60
videos → react-native-compressor: H.264 CRF 28, 720p max, 64k audio
audio → expo-audio's default Opus 32k (already compressed)
Documented in docs/media-sidecar.md (added later with Phase C PR).
Tests
- go test ./... green across 6 packages (blockchain consensus identity
media relay vm)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
132 lines
5.9 KiB
Modula-2
132 lines
5.9 KiB
Modula-2
module go-blockchain
|
|
|
|
go 1.25.0
|
|
|
|
require (
|
|
github.com/dgraph-io/badger/v4 v4.2.0
|
|
github.com/libp2p/go-libp2p v0.32.2
|
|
github.com/libp2p/go-libp2p-kad-dht v0.25.2
|
|
github.com/libp2p/go-libp2p-pubsub v0.10.0
|
|
github.com/multiformats/go-multiaddr v0.12.3
|
|
github.com/tetratelabs/wazero v1.7.3
|
|
golang.org/x/crypto v0.49.0
|
|
)
|
|
|
|
require (
|
|
golang.org/x/image v0.39.0
|
|
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect
|
|
)
|
|
|
|
require (
|
|
github.com/benbjohnson/clock v1.3.5 // indirect
|
|
github.com/beorn7/perks v1.0.1 // indirect
|
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
|
github.com/containerd/cgroups v1.1.0 // indirect
|
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
|
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
|
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
|
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
|
github.com/docker/go-units v0.5.0 // indirect
|
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
|
github.com/elastic/gosigar v0.14.2 // indirect
|
|
github.com/flynn/noise v1.0.0 // indirect
|
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
|
github.com/go-logr/logr v1.2.4 // indirect
|
|
github.com/go-logr/stdr v1.2.2 // indirect
|
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
|
github.com/gogo/protobuf v1.3.2 // indirect
|
|
github.com/golang/glog v1.0.0 // indirect
|
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
|
github.com/golang/protobuf v1.5.3 // indirect
|
|
github.com/golang/snappy v0.0.3 // indirect
|
|
github.com/google/flatbuffers v1.12.1 // indirect
|
|
github.com/google/gopacket v1.1.19 // indirect
|
|
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
|
|
github.com/google/uuid v1.3.0 // indirect
|
|
github.com/gorilla/websocket v1.5.0
|
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
|
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
|
|
github.com/huin/goupnp v1.3.0 // indirect
|
|
github.com/ipfs/boxo v0.10.0 // indirect
|
|
github.com/ipfs/go-cid v0.4.1 // indirect
|
|
github.com/ipfs/go-datastore v0.6.0 // indirect
|
|
github.com/ipfs/go-log v1.0.5 // indirect
|
|
github.com/ipfs/go-log/v2 v2.5.1 // indirect
|
|
github.com/ipld/go-ipld-prime v0.20.0 // indirect
|
|
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
|
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
|
|
github.com/jbenet/goprocess v0.1.4 // indirect
|
|
github.com/klauspost/compress v1.17.2 // indirect
|
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
|
github.com/koron/go-ssdp v0.0.4 // indirect
|
|
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
|
github.com/libp2p/go-cidranger v1.1.0 // indirect
|
|
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
|
|
github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect
|
|
github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect
|
|
github.com/libp2p/go-libp2p-record v0.2.0 // indirect
|
|
github.com/libp2p/go-libp2p-routing-helpers v0.7.2 // indirect
|
|
github.com/libp2p/go-msgio v0.3.0 // indirect
|
|
github.com/libp2p/go-nat v0.2.0 // indirect
|
|
github.com/libp2p/go-netroute v0.2.1 // indirect
|
|
github.com/libp2p/go-reuseport v0.4.0 // indirect
|
|
github.com/libp2p/go-yamux/v4 v4.0.1 // indirect
|
|
github.com/libp2p/zeroconf/v2 v2.2.0 // indirect
|
|
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
|
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
|
github.com/miekg/dns v1.1.56 // indirect
|
|
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
|
|
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
|
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
|
github.com/mr-tron/base58 v1.2.0 // indirect
|
|
github.com/multiformats/go-base32 v0.1.0 // indirect
|
|
github.com/multiformats/go-base36 v0.2.0 // indirect
|
|
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
|
|
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
|
|
github.com/multiformats/go-multibase v0.2.0 // indirect
|
|
github.com/multiformats/go-multicodec v0.9.0 // indirect
|
|
github.com/multiformats/go-multihash v0.2.3 // indirect
|
|
github.com/multiformats/go-multistream v0.5.0 // indirect
|
|
github.com/multiformats/go-varint v0.0.7 // indirect
|
|
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
|
|
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
|
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
|
github.com/pkg/errors v0.9.1 // indirect
|
|
github.com/polydawn/refmt v0.89.0 // indirect
|
|
github.com/prometheus/client_golang v1.16.0 // indirect
|
|
github.com/prometheus/client_model v0.4.0 // indirect
|
|
github.com/prometheus/common v0.44.0 // indirect
|
|
github.com/prometheus/procfs v0.11.1 // indirect
|
|
github.com/quic-go/qpack v0.4.0 // indirect
|
|
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
|
|
github.com/quic-go/quic-go v0.39.4 // indirect
|
|
github.com/quic-go/webtransport-go v0.6.0 // indirect
|
|
github.com/raulk/go-watchdog v1.3.0 // indirect
|
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
|
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
|
|
go.opencensus.io v0.24.0 // indirect
|
|
go.opentelemetry.io/otel v1.16.0 // indirect
|
|
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
|
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
|
go.uber.org/dig v1.17.1 // indirect
|
|
go.uber.org/fx v1.20.1 // indirect
|
|
go.uber.org/mock v0.3.0 // indirect
|
|
go.uber.org/multierr v1.11.0 // indirect
|
|
go.uber.org/zap v1.26.0 // indirect
|
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
|
golang.org/x/mod v0.34.0 // indirect
|
|
golang.org/x/net v0.52.0 // indirect
|
|
golang.org/x/sync v0.20.0 // indirect
|
|
golang.org/x/sys v0.42.0 // indirect
|
|
golang.org/x/text v0.36.0 // indirect
|
|
golang.org/x/tools v0.43.0 // indirect
|
|
gonum.org/v1/gonum v0.13.0 // indirect
|
|
google.golang.org/protobuf v1.31.0 // indirect
|
|
lukechampine.com/blake3 v1.2.1 // indirect
|
|
)
|