From 490f9d4a1dcef7f917ece8f238fafa6404eadd32 Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Thu, 2 Apr 2026 15:05:13 -0400 Subject: [PATCH] Rebrand SooSeF to FieldWitness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete project rebrand for better positioning in the press freedom and digital security space. FieldWitness communicates both field deployment and evidence testimony — appropriate for the target audience of journalists, NGOs, and human rights organizations. Rename mapping: - soosef → fieldwitness (package, CLI, all imports) - soosef.stegasoo → fieldwitness.stego - soosef.verisoo → fieldwitness.attest - ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name) - SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR - SoosefConfig → FieldWitnessConfig - SoosefError → FieldWitnessError Also includes: - License switch from MIT to GPL-3.0 - C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py - README repositioned to lead with provenance/federation, stego backgrounded - Threat model skeleton at docs/security/threat-model.md - Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/ci.yml | 10 +- CLAUDE.md | 99 +-- LICENSE | 674 ++++++++++++++++++ README.md | 519 ++++++++------ deploy/config-presets/README.md | 8 +- deploy/docker/Dockerfile | 42 +- deploy/docker/docker-compose.yml | 10 +- deploy/kubernetes/README.md | 8 +- deploy/kubernetes/namespace.yaml | 4 +- deploy/kubernetes/relay-deployment.yaml | 24 +- deploy/kubernetes/server-deployment.yaml | 32 +- deploy/live-usb/build.sh | 8 +- .../live/0100-install-soosef.hook.chroot | 26 +- .../config/hooks/live/0200-harden.hook.chroot | 12 +- .../etc/systemd/system/soosef.service | 18 +- .../includes.chroot/etc/xdg/openbox/autostart | 4 +- .../config/package-lists/soosef.list.chroot | 2 +- docker/Dockerfile | 46 +- docker/docker-compose.yml | 22 +- docker/entrypoint.sh | 14 +- docs/architecture/chain-format.md | 32 +- docs/architecture/export-bundle.md | 20 +- docs/architecture/federation-protocol.md | 10 +- docs/architecture/federation.md | 20 +- docs/deployment.md | 306 ++++---- docs/evidence-guide.md | 52 +- docs/federation.md | 42 +- docs/index.md | 4 +- docs/planning/c2pa-integration.md | 240 +++++++ docs/planning/gtm-feasibility.md | 214 ++++++ docs/security/threat-model.md | 480 +++++++++++++ docs/source-dropbox.md | 34 +- docs/training/admin-operations-guide.md | 108 +-- docs/training/admin-reference.md | 80 +-- docs/training/emergency-card.md | 6 +- docs/training/reporter-field-guide.md | 70 +- docs/training/reporter-quickstart.md | 8 +- frontends/web/app.py | 120 ++-- frontends/web/auth.py | 10 +- frontends/web/blueprints/admin.py | 2 +- frontends/web/blueprints/attest.py | 72 +- frontends/web/blueprints/dropbox.py | 14 +- frontends/web/blueprints/federation.py | 10 +- frontends/web/blueprints/fieldkit.py | 8 +- frontends/web/blueprints/keys.py | 8 +- frontends/web/blueprints/stego.py | 6 +- frontends/web/ssl_utils.py | 2 +- frontends/web/static/js/auth.js | 10 +- .../static/js/{soosef.js => fieldwitness.js} | 26 +- frontends/web/static/js/generate.js | 12 +- frontends/web/static/style.css | 6 +- frontends/web/stego_routes.py | 48 +- frontends/web/stego_worker.py | 34 +- frontends/web/subprocess_stego.py | 4 +- frontends/web/temp_storage.py | 6 +- frontends/web/templates/account.html | 12 +- .../web/templates/admin/password_reset.html | 2 +- frontends/web/templates/admin/settings.html | 4 +- .../web/templates/admin/user_created.html | 2 +- frontends/web/templates/admin/user_new.html | 2 +- frontends/web/templates/admin/users.html | 2 +- frontends/web/templates/attest/attest.html | 4 +- frontends/web/templates/attest/log.html | 2 +- frontends/web/templates/attest/record.html | 2 +- frontends/web/templates/attest/result.html | 2 +- frontends/web/templates/attest/verify.html | 2 +- .../web/templates/attest/verify_result.html | 2 +- frontends/web/templates/base.html | 14 +- frontends/web/templates/dropbox/admin.html | 4 +- .../web/templates/federation/status.html | 4 +- frontends/web/templates/fieldkit/keys.html | 8 +- .../web/templates/fieldkit/killswitch.html | 4 +- frontends/web/templates/fieldkit/status.html | 2 +- frontends/web/templates/index.html | 16 +- frontends/web/templates/login.html | 2 +- frontends/web/templates/recover.html | 6 +- .../web/templates/regenerate_recovery.html | 8 +- frontends/web/templates/setup.html | 6 +- frontends/web/templates/setup_recovery.html | 8 +- frontends/web/templates/stego/about.html | 6 +- frontends/web/templates/stego/decode.html | 6 +- frontends/web/templates/stego/encode.html | 4 +- .../web/templates/stego/encode_result.html | 4 +- frontends/web/templates/stego/generate.html | 12 +- frontends/web/templates/stego/tools.html | 2 +- pyproject.toml | 40 +- src/fieldwitness/__init__.py | 14 + src/fieldwitness/_availability.py | 31 + src/fieldwitness/api.py | 36 + src/{soosef => fieldwitness}/archive.py | 32 +- .../attest}/__init__.py | 10 +- .../verisoo => fieldwitness/attest}/api.py | 22 +- .../attest}/attestation.py | 8 +- .../verisoo => fieldwitness/attest}/binlog.py | 4 +- .../verisoo => fieldwitness/attest}/cli.py | 56 +- .../verisoo => fieldwitness/attest}/crypto.py | 2 +- .../verisoo => fieldwitness/attest}/embed.py | 68 +- .../attest}/exceptions.py | 16 +- .../attest}/federation.py | 10 +- .../attest}/hashing.py | 2 +- .../attest}/lmdb_store.py | 6 +- .../verisoo => fieldwitness/attest}/merkle.py | 2 +- .../verisoo => fieldwitness/attest}/models.py | 6 +- .../attest}/peer_store.py | 2 +- .../attest}/storage.py | 10 +- .../attest}/verification.py | 4 +- src/{soosef => fieldwitness}/audit.py | 8 +- src/fieldwitness/c2pa_bridge/__init__.py | 26 + src/fieldwitness/c2pa_bridge/cert.py | 214 ++++++ src/fieldwitness/c2pa_bridge/export.py | 389 ++++++++++ .../c2pa_bridge/vendor_assertions.py | 274 +++++++ src/{soosef => fieldwitness}/cli.py | 264 +++---- src/{soosef => fieldwitness}/config.py | 18 +- src/{soosef => fieldwitness}/evidence.py | 14 +- src/{soosef => fieldwitness}/exceptions.py | 18 +- .../federation/__init__.py | 6 +- .../federation/anchors.py | 8 +- .../federation/chain.py | 14 +- .../federation/entropy.py | 2 +- .../federation/exchange.py | 6 +- .../federation/models.py | 2 +- .../federation/serialization.py | 2 +- .../fieldkit/__init__.py | 6 +- .../fieldkit/deadman.py | 12 +- .../fieldkit/geofence.py | 6 +- .../fieldkit/killswitch.py | 24 +- .../fieldkit/tamper.py | 2 +- .../fieldkit/usb_monitor.py | 2 +- src/fieldwitness/keystore/__init__.py | 6 + .../keystore/export.py | 14 +- .../keystore/manager.py | 42 +- .../keystore/models.py | 2 +- src/{soosef => fieldwitness}/metadata.py | 6 +- src/{soosef => fieldwitness}/paths.py | 20 +- .../stego}/__init__.py | 6 +- .../stegasoo => fieldwitness/stego}/api.py | 86 +-- .../stego}/api_auth.py | 10 +- .../stego}/audio_steganography.py | 2 +- .../stego}/audio_utils.py | 2 +- .../stego}/backends/__init__.py | 4 +- .../stego}/backends/dct.py | 0 .../stego}/backends/lsb.py | 0 .../stego}/backends/protocol.py | 0 .../stego}/backends/registry.py | 4 +- .../stegasoo => fieldwitness/stego}/batch.py | 14 +- .../stego}/carrier_tracker.py | 2 +- .../stego}/channel.py | 28 +- .../stegasoo => fieldwitness/stego}/cli.py | 240 +++---- .../stego}/compression.py | 2 +- .../stego}/constants.py | 20 +- .../stegasoo => fieldwitness/stego}/crypto.py | 14 +- src/fieldwitness/stego/data/__init__.py | 1 + .../stego}/data/bip39-words.txt | 0 .../stego}/dct_steganography.py | 10 +- .../stegasoo => fieldwitness/stego}/debug.py | 16 +- .../stegasoo => fieldwitness/stego}/decode.py | 4 +- .../stegasoo => fieldwitness/stego}/encode.py | 4 +- .../stego}/exceptions.py | 18 +- .../stego}/generate.py | 2 +- .../stego}/image_utils.py | 2 +- .../stegasoo => fieldwitness/stego}/keygen.py | 2 +- .../stegasoo => fieldwitness/stego}/models.py | 2 +- .../stego}/platform_presets.py | 2 +- .../stegasoo => fieldwitness/stego}/py.typed | 0 .../stego}/qr_utils.py | 2 +- .../stego}/recovery.py | 14 +- .../stego}/spread_steganography.py | 0 .../stego}/steganalysis.py | 2 +- .../stego}/steganography.py | 18 +- .../stegasoo => fieldwitness/stego}/utils.py | 2 +- .../stego}/validation.py | 2 +- .../stego}/video_steganography.py | 6 +- .../stego}/video_utils.py | 6 +- src/soosef/__init__.py | 14 - src/soosef/_availability.py | 21 - src/soosef/api.py | 36 - src/soosef/keystore/__init__.py | 6 - src/soosef/stegasoo/data/__init__.py | 1 - tests/conftest.py | 12 +- ...isoo_hashing.py => test_attest_hashing.py} | 2 +- tests/test_chain.py | 18 +- tests/test_chain_security.py | 4 +- tests/test_deadman_enforcement.py | 46 +- tests/test_key_rotation.py | 34 +- tests/test_killswitch.py | 54 +- tests/test_serialization.py | 4 +- tests/{test_stegasoo.py => test_stego.py} | 48 +- ..._stegasoo_audio.py => test_stego_audio.py} | 108 +-- 188 files changed, 4588 insertions(+), 2017 deletions(-) create mode 100644 LICENSE create mode 100644 docs/planning/c2pa-integration.md create mode 100644 docs/planning/gtm-feasibility.md create mode 100644 docs/security/threat-model.md rename frontends/web/static/js/{soosef.js => fieldwitness.js} (98%) create mode 100644 src/fieldwitness/__init__.py create mode 100644 src/fieldwitness/_availability.py create mode 100644 src/fieldwitness/api.py rename src/{soosef => fieldwitness}/archive.py (87%) rename src/{soosef/verisoo => fieldwitness/attest}/__init__.py (53%) rename src/{soosef/verisoo => fieldwitness/attest}/api.py (97%) rename src/{soosef/verisoo => fieldwitness/attest}/attestation.py (99%) rename src/{soosef/verisoo => fieldwitness/attest}/binlog.py (99%) rename src/{soosef/verisoo => fieldwitness/attest}/cli.py (94%) rename src/{soosef/verisoo => fieldwitness/attest}/crypto.py (99%) rename src/{soosef/verisoo => fieldwitness/attest}/embed.py (87%) rename src/{soosef/verisoo => fieldwitness/attest}/exceptions.py (79%) rename src/{soosef/verisoo => fieldwitness/attest}/federation.py (98%) rename src/{soosef/verisoo => fieldwitness/attest}/hashing.py (99%) rename src/{soosef/verisoo => fieldwitness/attest}/lmdb_store.py (98%) rename src/{soosef/verisoo => fieldwitness/attest}/merkle.py (99%) rename src/{soosef/verisoo => fieldwitness/attest}/models.py (99%) rename src/{soosef/verisoo => fieldwitness/attest}/peer_store.py (99%) rename src/{soosef/verisoo => fieldwitness/attest}/storage.py (98%) rename src/{soosef/verisoo => fieldwitness/attest}/verification.py (99%) rename src/{soosef => fieldwitness}/audit.py (94%) create mode 100644 src/fieldwitness/c2pa_bridge/__init__.py create mode 100644 src/fieldwitness/c2pa_bridge/cert.py create mode 100644 src/fieldwitness/c2pa_bridge/export.py create mode 100644 src/fieldwitness/c2pa_bridge/vendor_assertions.py rename src/{soosef => fieldwitness}/cli.py (85%) rename src/{soosef => fieldwitness}/config.py (81%) rename src/{soosef => fieldwitness}/evidence.py (94%) rename src/{soosef => fieldwitness}/exceptions.py (56%) rename src/{soosef => fieldwitness}/federation/__init__.py (62%) rename src/{soosef => fieldwitness}/federation/anchors.py (95%) rename src/{soosef => fieldwitness}/federation/chain.py (98%) rename src/{soosef => fieldwitness}/federation/entropy.py (97%) rename src/{soosef => fieldwitness}/federation/exchange.py (97%) rename src/{soosef => fieldwitness}/federation/models.py (96%) rename src/{soosef => fieldwitness}/federation/serialization.py (97%) rename src/{soosef => fieldwitness}/fieldkit/__init__.py (50%) rename src/{soosef => fieldwitness}/fieldkit/deadman.py (94%) rename src/{soosef => fieldwitness}/fieldkit/geofence.py (95%) rename src/{soosef => fieldwitness}/fieldkit/killswitch.py (89%) rename src/{soosef => fieldwitness}/fieldkit/tamper.py (98%) rename src/{soosef => fieldwitness}/fieldkit/usb_monitor.py (98%) create mode 100644 src/fieldwitness/keystore/__init__.py rename src/{soosef => fieldwitness}/keystore/export.py (93%) rename src/{soosef => fieldwitness}/keystore/manager.py (90%) rename src/{soosef => fieldwitness}/keystore/models.py (93%) rename src/{soosef => fieldwitness}/metadata.py (97%) rename src/{soosef => fieldwitness}/paths.py (87%) rename src/{soosef/stegasoo => fieldwitness/stego}/__init__.py (98%) rename src/{soosef/stegasoo => fieldwitness/stego}/api.py (97%) rename src/{soosef/stegasoo => fieldwitness/stego}/api_auth.py (95%) rename src/{soosef/stegasoo => fieldwitness/stego}/audio_steganography.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/audio_utils.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/backends/__init__.py (89%) rename src/{soosef/stegasoo => fieldwitness/stego}/backends/dct.py (100%) rename src/{soosef/stegasoo => fieldwitness/stego}/backends/lsb.py (100%) rename src/{soosef/stegasoo => fieldwitness/stego}/backends/protocol.py (100%) rename src/{soosef/stegasoo => fieldwitness/stego}/backends/registry.py (96%) rename src/{soosef/stegasoo => fieldwitness/stego}/batch.py (98%) rename src/{soosef/stegasoo => fieldwitness/stego}/carrier_tracker.py (97%) rename src/{soosef/stegasoo => fieldwitness/stego}/channel.py (95%) rename src/{soosef/stegasoo => fieldwitness/stego}/cli.py (93%) rename src/{soosef/stegasoo => fieldwitness/stego}/compression.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/constants.py (96%) rename src/{soosef/stegasoo => fieldwitness/stego}/crypto.py (98%) create mode 100644 src/fieldwitness/stego/data/__init__.py rename src/{soosef/stegasoo => fieldwitness/stego}/data/bip39-words.txt (100%) rename src/{soosef/stegasoo => fieldwitness/stego}/dct_steganography.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/debug.py (94%) rename src/{soosef/stegasoo => fieldwitness/stego}/decode.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/encode.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/exceptions.py (93%) rename src/{soosef/stegasoo => fieldwitness/stego}/generate.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/image_utils.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/keygen.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/models.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/platform_presets.py (98%) rename src/{soosef/stegasoo => fieldwitness/stego}/py.typed (100%) rename src/{soosef/stegasoo => fieldwitness/stego}/qr_utils.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/recovery.py (96%) rename src/{soosef/stegasoo => fieldwitness/stego}/spread_steganography.py (100%) rename src/{soosef/stegasoo => fieldwitness/stego}/steganalysis.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/steganography.py (98%) rename src/{soosef/stegasoo => fieldwitness/stego}/utils.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/validation.py (99%) rename src/{soosef/stegasoo => fieldwitness/stego}/video_steganography.py (98%) rename src/{soosef/stegasoo => fieldwitness/stego}/video_utils.py (99%) delete mode 100644 src/soosef/__init__.py delete mode 100644 src/soosef/_availability.py delete mode 100644 src/soosef/api.py delete mode 100644 src/soosef/keystore/__init__.py delete mode 100644 src/soosef/stegasoo/data/__init__.py rename tests/{test_verisoo_hashing.py => test_attest_hashing.py} (96%) rename tests/{test_stegasoo.py => test_stego.py} (94%) rename tests/{test_stegasoo_audio.py => test_stego_audio.py} (87%) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 4d80e60..d3ba013 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: run: apt-get update && apt-get install -y --no-install-recommends git - name: Checkout run: | - git clone --depth=1 --branch="${GITHUB_REF_NAME}" https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE" || git clone --depth=1 https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE" + git clone --depth=1 --branch="${GITHUB_REF_NAME}" https://git.golfcards.club/alee/fieldwitness.git "$GITHUB_WORKSPACE" || git clone --depth=1 https://git.golfcards.club/alee/fieldwitness.git "$GITHUB_WORKSPACE" - run: pip install ruff black - name: Check formatting run: black --check --target-version py312 src/ tests/ frontends/ @@ -31,12 +31,12 @@ jobs: run: apt-get update && apt-get install -y --no-install-recommends git - name: Checkout run: | - git clone --depth=1 --branch="${GITHUB_REF_NAME}" https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE" || git clone --depth=1 https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE" + git clone --depth=1 --branch="${GITHUB_REF_NAME}" https://git.golfcards.club/alee/fieldwitness.git "$GITHUB_WORKSPACE" || git clone --depth=1 https://git.golfcards.club/alee/fieldwitness.git "$GITHUB_WORKSPACE" - run: pip install mypy - name: Typecheck run: mypy src/ - # TODO: Re-enable once stegasoo/verisoo are available from git.golfcards.club + # TODO: Re-enable once stego/attest are available from git.golfcards.club # test: # runs-on: ubuntu-latest # container: @@ -46,8 +46,8 @@ jobs: # run: apt-get update && apt-get install -y --no-install-recommends git # - name: Checkout # run: | - # git clone --depth=1 --branch="${GITHUB_REF_NAME}" https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE" || git clone --depth=1 https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE" + # git clone --depth=1 --branch="${GITHUB_REF_NAME}" https://git.golfcards.club/alee/fieldwitness.git "$GITHUB_WORKSPACE" || git clone --depth=1 https://git.golfcards.club/alee/fieldwitness.git "$GITHUB_WORKSPACE" # - name: Install dependencies # run: pip install -e ".[dev]" # - name: Run tests - # run: pytest --cov=soosef --cov-report=term-missing + # run: pytest --cov=fieldwitness --cov-report=term-missing diff --git a/CLAUDE.md b/CLAUDE.md index 52a7377..508f89e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,14 +1,19 @@ -# SooSeF -- Claude Code Project Guide +# FieldWitness -- Claude Code Project Guide -SooSeF (Soo Security Fieldkit) is an offline-first security toolkit for journalists, NGOs, -and at-risk organizations. Monorepo consolidating Stegasoo and Verisoo as subpackages. +FieldWitness (FieldWitness) is an offline-first provenance attestation and gossip +federation system for journalists, NGOs, and at-risk organizations. It establishes +cryptographic chain-of-custody over evidence in airgapped and resource-constrained +environments, syncs attestations across organizational boundaries via a gossip protocol +with Merkle consistency proofs, and produces court-ready evidence packages with standalone +verification. Steganography (Stego) and provenance attestation (Attest) are included +as subpackages in this monorepo. -Version 0.2.0 · Python >=3.11 · MIT License +Version 0.2.0 · Python >=3.11 · GPL-3.0 License ## Quick commands ```bash -# Development install (single command -- stegasoo and verisoo are inlined subpackages) +# Development install (single command -- stego and attest are inlined subpackages) pip install -e ".[dev]" pytest # Run tests @@ -25,20 +30,20 @@ mypy src/ # Type check ## Architecture ``` -src/soosef/ Core library +src/fieldwitness/ Core library __init__.py Package init, __version__ (0.2.0) - _availability.py Runtime checks for optional subpackages (has_stegasoo, has_verisoo) - api.py Optional unified FastAPI app (uvicorn soosef.api:app) - audit.py Append-only JSON-lines audit log (~/.soosef/audit.jsonl) - cli.py Click CLI entry point (soosef command) - paths.py All ~/.soosef/* path constants (single source of truth, lazy resolution) - config.py Unified config loader (SoosefConfig dataclass + JSON) - exceptions.py SoosefError, ChainError, ChainIntegrityError, ChainAppendError, KeystoreError + _availability.py Runtime checks for optional subpackages (has_stego, has_attest) + api.py Optional unified FastAPI app (uvicorn fieldwitness.api:app) + audit.py Append-only JSON-lines audit log (~/.fieldwitness/audit.jsonl) + cli.py Click CLI entry point (fieldwitness command) + paths.py All ~/.fieldwitness/* path constants (single source of truth, lazy resolution) + config.py Unified config loader (FieldWitnessConfig dataclass + JSON) + exceptions.py FieldWitnessError, ChainError, ChainIntegrityError, ChainAppendError, KeystoreError metadata.py Extract-then-strip EXIF pipeline with field classification evidence.py Self-contained evidence package export (ZIP with verify.py) archive.py Cold archive export for long-term preservation (OAIS-aligned) - stegasoo/ Steganography engine (inlined from stegasoo v4.3.0) + stego/ Steganography engine (inlined from fieldwitness.stego v4.3.0) encode.py / decode.py Core encode/decode API generate.py Cover image generation crypto.py AES-256-GCM encryption, channel fingerprints @@ -55,14 +60,14 @@ src/soosef/ Core library validation.py Input validation models.py Data models constants.py Magic bytes, version constants, AUDIO_ENABLED, VIDEO_ENABLED - cli.py Stegasoo-specific CLI commands - api.py / api_auth.py Stegasoo REST API + auth + cli.py Stego-specific CLI commands + api.py / api_auth.py Stego REST API + auth carrier_tracker.py Carrier image reuse tracking (warns on reuse) platform_presets.py Social-media-aware encoding presets image_utils.py / audio_utils.py / video_utils.py keygen.py / qr_utils.py / recovery.py / debug.py / utils.py - verisoo/ Provenance attestation engine (inlined from verisoo v0.1.0) + attest/ Provenance attestation engine (inlined from fieldwitness.attest v0.1.0) attestation.py Core attestation creation + EXIF extraction verification.py Attestation verification crypto.py Ed25519 signing @@ -75,9 +80,9 @@ src/soosef/ Core library federation.py GossipNode, HttpTransport, PeerInfo, SyncStatus peer_store.py SQLite-backed peer persistence for federation models.py Attestation, AttestationRecord, ImageHashes, Identity - exceptions.py VerisooError, AttestationError, VerificationError, FederationError - cli.py Verisoo-specific CLI commands - api.py Verisoo REST API + federation endpoints + exceptions.py AttestError, AttestationError, VerificationError, FederationError + cli.py Attest-specific CLI commands + api.py Attest REST API + federation endpoints federation/ Federated attestation chain system chain.py ChainStore -- append-only hash chain with key rotation/recovery/delivery-ack @@ -103,7 +108,7 @@ frontends/web/ Unified Flask web UI app.py App factory (create_app()), ~36k -- mounts all blueprints auth.py SQLite3 multi-user auth with lockout + rate limiting temp_storage.py File-based temp storage with expiry - subprocess_stego.py Crash-safe subprocess isolation for stegasoo + subprocess_stego.py Crash-safe subprocess isolation for stego stego_worker.py Background stego processing stego_routes.py Stego route helpers (~87k) ssl_utils.py Self-signed HTTPS cert generation (cover_name support) @@ -119,7 +124,7 @@ frontends/web/ Unified Flask web UI dropbox/admin.html Drop box admin panel federation/status.html Federation peer dashboard -frontends/cli/ CLI package init (main entry point is src/soosef/cli.py) +frontends/cli/ CLI package init (main entry point is src/fieldwitness/cli.py) deploy/ Deployment artifacts docker/ Dockerfile (multi-stage: builder, relay, server) + docker-compose.yml @@ -135,7 +140,7 @@ docs/ Documentation architecture/ federation.md System architecture overview (threat model, layers, key domains) chain-format.md Chain record spec (CBOR, entropy witnesses, serialization) - export-bundle.md Export bundle spec (SOOSEFX1 binary format, envelope encryption) + export-bundle.md Export bundle spec (FIELDWITNESSX1 binary format, envelope encryption) federation-protocol.md Federation server protocol (CT-inspired, gossip, storage tiers) training/ reporter-quickstart.md One-page reporter quick-start for Tier 1 USB (print + laminate) @@ -160,40 +165,40 @@ Reporter in the field Newsroom / NGO office Friendly jurisdiction ## Dependency model -Stegasoo and Verisoo are inlined subpackages, not separate pip packages: -- `from soosef.stegasoo import encode` for steganography -- `from soosef.verisoo import Attestation` for provenance attestation -- Never `import stegasoo` or `import verisoo` directly -- `_availability.py` provides `has_stegasoo()` / `has_verisoo()` for graceful degradation +Stego and Attest are inlined subpackages, not separate pip packages: +- `from fieldwitness.stego import encode` for steganography +- `from fieldwitness.attest import Attestation` for provenance attestation +- Never `import fieldwitness.stego` or `import fieldwitness.attest` directly +- `_availability.py` provides `has_stego()` / `has_attest()` for graceful degradation when optional extras are not installed ## Key design decisions -- **Two key domains, never merged**: Stegasoo AES-256-GCM (derived from factors) and - Verisoo Ed25519 (signing identity) are separate security concerns +- **Two key domains, never merged**: Stego AES-256-GCM (derived from factors) and + Attest Ed25519 (signing identity) are separate security concerns - **Extract-then-strip model**: Stego strips all EXIF (carrier is vessel); attestation extracts evidentiary EXIF (GPS, timestamp) then strips dangerous fields (device serial) -- **subprocess_stego.py copies verbatim** from stegasoo -- it's a crash-safety boundary -- **All state under ~/.soosef/** -- one directory to back up, one to destroy. - `SOOSEF_DATA_DIR` env var relocates everything (cover mode, USB mode) +- **subprocess_stego.py copies verbatim** from fieldwitness.stego -- it's a crash-safety boundary +- **All state under ~/.fieldwitness/** -- one directory to back up, one to destroy. + `FIELDWITNESS_DATA_DIR` env var relocates everything (cover mode, USB mode) - **Offline-first**: All static assets vendored, no CDN. pip wheels bundled for airgap install - **Flask blueprints**: stego, attest, fieldkit, keys, admin, dropbox, federation - **Flask-WTF**: CSRF protection on all form endpoints; drop box is CSRF-exempt (sources don't have sessions) - **Client-side SHA-256**: Drop box upload page uses SubtleCrypto for pre-upload hashing - **Waitress**: Production WSGI server (replaces dev-only Flask server) -- **FastAPI option**: `soosef.api` provides a REST API alternative to the Flask web UI +- **FastAPI option**: `fieldwitness.api` provides a REST API alternative to the Flask web UI - **Pluggable backends**: Stego backends (LSB, DCT) registered via `backends/registry.py` - **ImageHashes generalized**: phash/dhash now optional, enabling non-image attestation - **Lazy path resolution**: All paths in paths.py resolve lazily via `__getattr__` from - `BASE_DIR` so that runtime overrides (--data-dir, SOOSEF_DATA_DIR) propagate correctly -- **Two-way federation**: Delivery acknowledgment records (`soosef/delivery-ack-v1`) + `BASE_DIR` so that runtime overrides (--data-dir, FIELDWITNESS_DATA_DIR) propagate correctly +- **Two-way federation**: Delivery acknowledgment records (`fieldwitness/delivery-ack-v1`) enable handshake proof - **Chain record types** (in federation/chain.py): - - `CONTENT_TYPE_KEY_ROTATION = "soosef/key-rotation-v1"` -- signed by OLD key - - `CONTENT_TYPE_KEY_RECOVERY = "soosef/key-recovery-v1"` -- signed by NEW key - - `CONTENT_TYPE_DELIVERY_ACK = "soosef/delivery-ack-v1"` -- signed by receiver -- **Gossip federation** (verisoo/federation.py): GossipNode with async peer sync, + - `CONTENT_TYPE_KEY_ROTATION = "fieldwitness/key-rotation-v1"` -- signed by OLD key + - `CONTENT_TYPE_KEY_RECOVERY = "fieldwitness/key-recovery-v1"` -- signed by NEW key + - `CONTENT_TYPE_DELIVERY_ACK = "fieldwitness/delivery-ack-v1"` -- signed by receiver +- **Gossip federation** (attest/federation.py): GossipNode with async peer sync, consistency proofs, HttpTransport over aiohttp. PeerStore for SQLite-backed persistence - **Threat level presets**: deploy/config-presets/ with low/medium/high/critical configs - **Selective disclosure**: Chain records can be exported with non-selected records @@ -203,18 +208,18 @@ Stegasoo and Verisoo are inlined subpackages, not separate pip packages: - **Transport-aware stego**: --transport whatsapp|signal|telegram auto-selects DCT/JPEG and pre-resizes carrier for platform survival -## Data directory layout (`~/.soosef/`) +## Data directory layout (`~/.fieldwitness/`) ``` -~/.soosef/ - config.json Unified configuration (SoosefConfig dataclass) +~/.fieldwitness/ + config.json Unified configuration (FieldWitnessConfig dataclass) audit.jsonl Append-only audit trail (JSON-lines) carrier_history.json Carrier reuse tracking database identity/ Ed25519 keypair (private.pem, public.pem, identity.meta.json) archived/ Timestamped old keypairs from rotations - stegasoo/ Channel key (channel.key) + stego/ Channel key (channel.key) archived/ Timestamped old channel keys from rotations - attestations/ Verisoo attestation store + attestations/ Attest attestation store log.bin Binary attestation log index/ LMDB index peers.json Legacy peer file @@ -223,7 +228,7 @@ Stegasoo and Verisoo are inlined subpackages, not separate pip packages: chain/ Hash chain (chain.bin, state.cbor) anchors/ External timestamp anchors (JSON files) auth/ Web UI auth databases - soosef.db User accounts + fieldwitness.db User accounts dropbox.db Drop box tokens + receipts certs/ Self-signed TLS certificates (cert.pem, key.pem) fieldkit/ Fieldkit state @@ -253,4 +258,4 @@ pytest tests/test_chain.py # Chain-specific Test files: `test_chain.py`, `test_chain_security.py`, `test_deadman_enforcement.py`, `test_key_rotation.py`, `test_killswitch.py`, `test_serialization.py`, -`test_stegasoo_audio.py`, `test_stegasoo.py`, `test_verisoo_hashing.py` +`test_stego_audio.py`, `test_stego.py`, `test_attest_hashing.py` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 2b2f334..7740061 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,54 @@ -# SooSeF -- Soo Security Fieldkit +# FieldWitness -- FieldWitness -**Offline-first security toolkit for journalists, NGOs, and at-risk organizations.** +**Offline-first provenance attestation with gossip federation for journalists, NGOs, and at-risk organizations.** ![Version](https://img.shields.io/badge/version-0.2.0-blue) ![Python](https://img.shields.io/badge/python-%3E%3D3.11-blue) -![License](https://img.shields.io/badge/license-MIT-green) +![License](https://img.shields.io/badge/license-GPL--3.0-blue) --- -## What is SooSeF? +## What is FieldWitness? -SooSeF combines steganography, provenance attestation, and field security tools into a -single package designed for airgapped and resource-constrained environments. It lets you: +FieldWitness is a field-deployable evidence integrity system. It lets journalists, human rights +documenters, and NGOs establish cryptographic chain-of-custody over photos, documents, and +sensor data -- in airgapped environments, across organizational boundaries, and in adversarial +conditions where data may need to be destroyed on demand. -- **Hide messages** in images, audio, and video using multiple steganographic techniques, with transport-aware encoding for lossy channels (WhatsApp, Signal, Telegram) -- **Prove authenticity** of photos, documents, and arbitrary files with Ed25519 signatures, Merkle-style hash chains, and RFC 3161 trusted timestamps -- **Protect data in the field** with a killswitch (including deep forensic scrub and self-uninstall), dead man's switch with webhook warnings, tamper detection, USB device whitelisting, and GPS geofencing -- **Manage cryptographic keys** with identity rotation, channel key generation, encrypted key bundle export/import, QR code sharing, trust store management, and identity recovery from chain -- **Federate attestations** across organizations with signed exchange bundles, delivery acknowledgments, selective disclosure for legal discovery, and investigation namespaces -- **Accept anonymous submissions** through a SecureDrop-style source drop box with token-gated uploads, client-side SHA-256 hashing, and automatic EXIF extraction/stripping -- **Preserve evidence long-term** with self-contained evidence packages and OAIS-aligned cold archives that include standalone verification scripts and algorithm documentation +The core claim: a file attested with FieldWitness can be handed to a court or partner organization +with proof that it has not been altered, proof of when it was captured (anchored externally +via RFC 3161), and proof of who held it -- without requiring the verifying party to install +FieldWitness or trust any central authority. -Stegasoo (steganography, v4.3.0) and Verisoo (attestation, v0.1.0) are included as -subpackages (`import soosef.stegasoo`, `import soosef.verisoo`). Everything ships as one -install: `pip install soosef`. +**Three-tier deployment model:** + +``` +Tier 1: Field Device Tier 2: Org Server Tier 3: Federation Relay +(Bootable USB + laptop) (Docker on mini PC / VPS) (Docker on VPS) + +Reporter in the field Newsroom / NGO office Friendly jurisdiction +Amnesic, LUKS-encrypted Persistent storage Attestation sync only +Pull USB = zero trace Web UI + federation API Zero knowledge of keys + \ | / + \_____ sneakernet ____+____ gossip API ____/ +``` + +Stego (steganography, v4.3.0) and Attest (attestation, v0.1.0) are included as +subpackages (`import fieldwitness.stego`, `import fieldwitness.attest`). Everything ships as one +install: `pip install fieldwitness`. --- ## Quick Start ```bash -pip install "soosef[web,cli]" -soosef init -soosef serve +pip install "fieldwitness[web,cli]" +fieldwitness init +fieldwitness serve ``` -This creates the `~/.soosef/` directory structure, generates an Ed25519 identity and +This creates the `~/.fieldwitness/` directory structure, generates an Ed25519 identity and channel key, writes a default config, and starts an HTTPS web UI on `https://127.0.0.1:5000`. @@ -44,58 +56,46 @@ channel key, writes a default config, and starts an HTTPS web UI on ## Features -### Steganography (Stegasoo) - -- **LSB encoding** -- bit-level message hiding in PNG images -- **DCT encoding** -- frequency-domain hiding in JPEG images (requires `stego-dct` extra) -- **Audio steganography** -- hide data in WAV/FLAC audio (requires `stego-audio` extra) -- **Video steganography** -- frame-level encoding -- **Transport-aware encoding** -- `--transport whatsapp|signal|telegram|discord|email|direct` auto-selects the right encoding mode and carrier resolution for lossy messaging platforms. WhatsApp/Signal/Telegram force DCT/JPEG mode and pre-resize the carrier to survive recompression -- **Carrier reuse tracking** -- warns when a carrier image has been used before, since comparing two versions of the same carrier trivially reveals steganographic modification -- AES-256-GCM encryption with Argon2id key derivation -- EXIF stripping on encode to prevent metadata leakage -- Compression support (zstandard, optional LZ4) - -### Attestation (Verisoo) +### Attestation and Provenance (Attest) - Ed25519 digital signatures for images and arbitrary files (CSV, documents, sensor data) -- Perceptual hashing (pHash, dHash) for tamper-evident photo attestation; SHA-256-only mode for non-image files +- Perceptual hashing (pHash, dHash) for tamper-evident photo attestation -- identifies + re-uploaded or re-compressed copies. SHA-256-only mode for non-image files +- Append-only hash chain (CBOR-encoded) with Merkle tree verification -- every attestation + is chained to all prior attestations, making retroactive tampering detectable - LMDB-backed attestation storage -- Append-only hash chain (CBOR-encoded) with Merkle tree verification - Batch attestation for directories - **Investigation namespaces** -- tag and filter attestations by case or project - **Derived work lineage** -- parent-child attestation tracking for editorial workflows -- **Chain position proof** -- verification receipts include the record's position in the hash chain +- **Chain position proof** -- verification receipts include the record's position in the + hash chain ### Extract-Then-Strip EXIF Pipeline -Resolves the tension between steganography (strip everything to protect sources) and -attestation (preserve everything to prove provenance): +Resolves the tension between protecting sources (strip everything) and proving provenance +(preserve everything): 1. Extract all EXIF metadata from the original image bytes -2. Classify fields as **evidentiary** (GPS coordinates, timestamp -- valuable for provenance) or **dangerous** (device serial number, firmware version -- could identify the source) +2. Classify fields as **evidentiary** (GPS coordinates, timestamp -- valuable for + provenance) or **dangerous** (device serial number, firmware version -- could identify + the source) 3. Preserve evidentiary fields in the attestation record 4. Strip all metadata from the stored/display copy -### Cross-Organization Federation +### Gossip Federation -- **Attestation exchange** -- export signed bundles of attestation records and chain data for offline transfer to partner organizations -- **Delivery acknowledgments** -- when an organization imports a bundle, a `soosef/delivery-ack-v1` chain record is signed and can be shared back, creating a two-way federation handshake -- **Trust store** -- import collaborator Ed25519 public keys; only records signed by trusted keys are imported during federation -- **Investigation filtering** -- export/import only records tagged with a specific investigation - -### External Timestamp Anchoring - -Two mechanisms to externally prove that the chain head existed before a given time: - -- **RFC 3161 TSA** -- automated submission to any RFC 3161 Timestamping Authority (e.g., FreeTSA). The signed timestamp token is saved alongside the chain -- **Manual anchors** -- export the chain head hash as a compact string for manual submission to any external witness (blockchain transaction, newspaper classified, tweet, email to a TSA) - -A single anchor for the chain head implicitly timestamps every record that preceded it, because the chain is append-only with hash linkage. - -### Selective Disclosure - -Produce verifiable proofs for specific chain records while keeping others redacted. Selected records are included in full; non-selected records appear only as hashes. A third party can verify that the disclosed records are part of an unbroken chain without seeing the contents of other records. Designed for legal discovery, court orders, and FOIA responses. +- **Gossip protocol** -- nodes periodically exchange Merkle roots with peers. Divergence + triggers a consistency proof request and incremental record fetch. No central + coordinator, no consensus, no leader election -- just append-only logs that converge +- **Attestation exchange** -- export signed bundles of attestation records and chain data + for offline transfer (sneakernet) to partner organizations +- **Delivery acknowledgments** -- when an organization imports a bundle, a + `fieldwitness/delivery-ack-v1` chain record is signed and can be shared back, creating a + two-way federation handshake +- **Trust store** -- import collaborator Ed25519 public keys; only records signed by + trusted keys are imported during federation +- **Investigation filtering** -- export/import only records tagged with a specific + investigation ### Evidence Packages @@ -105,12 +105,14 @@ Self-contained ZIP bundles for handing evidence to lawyers, courts, or archives: - Attestation records with full Ed25519 signatures - Chain segment with hash linkage - Signer's public key -- `verify.py` -- standalone verification script that requires only Python 3.11+ and the `cryptography` pip package (no SooSeF installation needed) +- `verify.py` -- standalone verification script that requires only Python 3.11+ and the + `cryptography` pip package (no FieldWitness installation needed) - Human-readable README ### Cold Archive -Full-state export for long-term evidence preservation (10+ year horizon), aligned with OAIS (ISO 14721): +Full-state export for long-term evidence preservation (10+ year horizon), aligned with +OAIS (ISO 14721): - Raw chain binary and state checkpoint - Attestation log and LMDB index @@ -121,40 +123,95 @@ Full-state export for long-term evidence preservation (10+ year horizon), aligne - `verify.py` standalone verifier - `manifest.json` with SHA-256 integrity hashes of key files +### External Timestamp Anchoring + +Two mechanisms to prove the chain head existed before a given time: + +- **RFC 3161 TSA** -- automated submission to any RFC 3161 Timestamping Authority (e.g., + FreeTSA). The signed timestamp token is saved alongside the chain +- **Manual anchors** -- export the chain head hash as a compact string for manual + submission to any external witness (blockchain transaction, newspaper classified, email + to a TSA) + +A single anchor for the chain head implicitly timestamps every record that preceded it, +because the chain is append-only with hash linkage. + +### Selective Disclosure + +Produce verifiable proofs for specific chain records while keeping others redacted. +Selected records are included in full; non-selected records appear only as hashes. A third +party can verify that the disclosed records are part of an unbroken chain without seeing +the contents of other records. Designed for legal discovery, court orders, and FOIA +responses. + +### Field Security (Fieldkit) + +- **Killswitch** -- emergency destruction of all data under `~/.fieldwitness/`, ordered by + sensitivity (keys first, then data, then logs). Includes: + - **Deep forensic scrub** -- removes `__pycache__`, `.pyc`, pip `dist-info`, pip + download cache, and scrubs shell history entries containing "fieldwitness" + - **Self-uninstall** -- runs `pip uninstall -y fieldwitness` as the final step + - **System log clearing** -- best-effort journald vacuum on Linux +- **Dead man's switch** -- automated purge if check-in is missed, with a configurable + grace period. During the grace period, a **webhook warning** is sent (POST to a + configured URL) and a local warning file is written before the killswitch fires +- **Tamper detection** -- file integrity monitoring with baseline snapshots +- **USB whitelist** -- block or alert on unauthorized USB devices (Linux/pyudev) +- **Geofence** -- GPS boundary enforcement with configurable radius. Supports live GPS via + **gpsd** (`get_current_location()` connects to `127.0.0.1:2947`) +- **Hardware killswitch** -- GPIO pin monitoring for Raspberry Pi physical button + (configurable pin and hold duration) + ### Source Drop Box -SecureDrop-style anonymous intake built into the SooSeF web UI: +SecureDrop-style anonymous intake built into the FieldWitness web UI: - Admin creates a time-limited upload token with a configurable file limit -- Source opens the token URL (no account or SooSeF branding -- source safety) -- **Client-side SHA-256** via SubtleCrypto runs in the browser before upload, so the source can independently verify what they submitted +- Source opens the token URL (no account or FieldWitness branding -- source safety) +- **Client-side SHA-256** via SubtleCrypto runs in the browser before upload, so the + source can independently verify what they submitted - Files are run through the extract-then-strip EXIF pipeline and auto-attested on receipt - Source receives HMAC-derived receipt codes that prove delivery - Tokens and receipts are stored in SQLite; tokens auto-expire ### Key Rotation and Recovery -- **Key rotation** -- both identity (Ed25519) and channel (AES) keys can be rotated. The chain records the rotation as a `soosef/key-rotation-v1` record signed by the OLD key, creating a cryptographic trust chain -- **Identity recovery** -- after device loss, a new key can be introduced via a `soosef/key-recovery-v1` chain record. The record carries the old fingerprint and optional cosigner fingerprints for audit -- **Channel key only export** -- share just the channel key (not identity keys) with collaborators via encrypted file or QR code (`soosef-channel:` URI scheme) +- **Key rotation** -- both identity (Ed25519) and channel (AES) keys can be rotated. The + chain records the rotation as a `fieldwitness/key-rotation-v1` record signed by the OLD key, + creating a cryptographic trust chain +- **Identity recovery** -- after device loss, a new key can be introduced via a + `fieldwitness/key-recovery-v1` chain record. The record carries the old fingerprint and + optional cosigner fingerprints for audit +- **Channel key only export** -- share just the channel key (not identity keys) with + collaborators via encrypted file or QR code (`fieldwitness-channel:` URI scheme) - **Backup tracking** -- records when the last backup was taken and warns when overdue -### Fieldkit - -- **Killswitch** -- emergency destruction of all data under `~/.soosef/`, ordered by sensitivity (keys first, then data, then logs). Includes: - - **Deep forensic scrub** -- removes `__pycache__`, `.pyc`, pip `dist-info`, pip download cache, and scrubs shell history entries containing "soosef" - - **Self-uninstall** -- runs `pip uninstall -y soosef` as the final step - - **System log clearing** -- best-effort journald vacuum on Linux -- **Dead man's switch** -- automated purge if check-in is missed, with a configurable grace period. During the grace period, a **webhook warning** is sent (POST to a configured URL) and a local warning file is written before the killswitch fires -- **Tamper detection** -- file integrity monitoring with baseline snapshots -- **USB whitelist** -- block or alert on unauthorized USB devices (Linux/pyudev) -- **Geofence** -- GPS boundary enforcement with configurable radius. Supports live GPS via **gpsd** (`get_current_location()` connects to `127.0.0.1:2947`) -- **Hardware killswitch** -- GPIO pin monitoring for Raspberry Pi physical button (configurable pin and hold duration) - ### Cover / Duress Mode -- **Configurable certificate CN** -- set `cover_name` in config to replace "SooSeF Local" in the self-signed TLS certificate -- **Portable data directory** -- set `SOOSEF_DATA_DIR` to relocate all state to an arbitrary path (e.g., an innocuously named directory on a USB stick). All paths resolve lazily from `BASE_DIR`, so runtime overrides propagate correctly +- **Configurable certificate CN** -- set `cover_name` in config to replace "FieldWitness Local" + in the self-signed TLS certificate +- **Portable data directory** -- set `FIELDWITNESS_DATA_DIR` to relocate all state to an + arbitrary path (e.g., an innocuously named directory on a USB stick). All paths resolve + lazily from `BASE_DIR`, so runtime overrides propagate correctly + +### Additional Tools: Steganography (Stego) + +For situations requiring covert channels -- hiding communications or small payloads inside +ordinary media files: + +- **LSB encoding** -- bit-level message hiding in PNG images +- **DCT encoding** -- frequency-domain hiding in JPEG images (requires `stego-dct` extra) +- **Audio steganography** -- hide data in WAV/FLAC audio (requires `stego-audio` extra) +- **Video steganography** -- frame-level encoding +- **Transport-aware encoding** -- `--transport whatsapp|signal|telegram|discord|email|direct` + auto-selects the right encoding mode and carrier resolution for lossy messaging + platforms. WhatsApp/Signal/Telegram force DCT/JPEG mode and pre-resize the carrier to + survive recompression +- **Carrier reuse tracking** -- warns when a carrier image has been used before, since + comparing two versions of the same carrier trivially reveals steganographic modification +- AES-256-GCM encryption with Argon2id key derivation +- EXIF stripping on encode to prevent metadata leakage +- Compression support (zstandard, optional LZ4) --- @@ -163,15 +220,15 @@ SecureDrop-style anonymous intake built into the SooSeF web UI: ### Basic install (core library only) ```bash -pip install soosef +pip install fieldwitness ``` ### With extras ```bash -pip install "soosef[web,cli]" # Web UI + CLI (most common) -pip install "soosef[all]" # Everything except dev tools -pip install "soosef[dev]" # All + pytest, black, ruff, mypy +pip install "fieldwitness[web,cli]" # Web UI + CLI (most common) +pip install "fieldwitness[all]" # Everything except dev tools +pip install "fieldwitness[dev]" # All + pytest, black, ruff, mypy ``` ### Available extras @@ -197,35 +254,24 @@ Bundle wheels on a networked machine, then install offline: ```bash # On networked machine -pip download "soosef[web,cli]" -d ./wheels +pip download "fieldwitness[web,cli]" -d ./wheels # Transfer ./wheels to target via USB # On airgapped machine -pip install --no-index --find-links=./wheels "soosef[web,cli]" -soosef init -soosef serve --host 0.0.0.0 +pip install --no-index --find-links=./wheels "fieldwitness[web,cli]" +fieldwitness init +fieldwitness serve --host 0.0.0.0 ``` --- ## Deployment -SooSeF uses a three-tier deployment model designed for field journalism, organizational +FieldWitness uses a three-tier deployment model designed for field journalism, organizational evidence management, and cross-organization federation. -``` -Tier 1: Field Device Tier 2: Org Server Tier 3: Federation Relay -(Bootable USB + laptop) (Docker on mini PC / VPS) (Docker on VPS) - -Reporter in the field Newsroom / NGO office Friendly jurisdiction -Amnesic, LUKS-encrypted Persistent storage Attestation sync only -Pull USB = zero trace Web UI + federation API Zero knowledge of keys - \ | / - \_____ sneakernet ____+____ gossip API ____/ -``` - **Tier 1 -- Field Device.** A bootable Debian Live USB stick. Boots into a minimal desktop -with Firefox pointed at the local SooSeF web UI. LUKS-encrypted persistent partition. Pull +with Firefox pointed at the local FieldWitness web UI. LUKS-encrypted persistent partition. Pull the USB and the host machine retains nothing. **Tier 2 -- Org Server.** A Docker deployment on a mini PC or trusted VPS. Runs the full @@ -251,7 +297,7 @@ cd deploy/docker && docker compose up relay -d ### Threat level configuration presets -SooSeF ships four configuration presets at `deploy/config-presets/`: +FieldWitness ships four configuration presets at `deploy/config-presets/`: | Preset | Session | Killswitch | Dead Man | Cover Name | |---|---|---|---|---| @@ -261,7 +307,7 @@ SooSeF ships four configuration presets at `deploy/config-presets/`: | `critical-threat.json` | 3 min | On | 6h / 1h grace | "System Statistics" | ```bash -cp deploy/config-presets/high-threat.json ~/.soosef/config.json +cp deploy/config-presets/high-threat.json ~/.fieldwitness/config.json ``` See [docs/deployment.md](docs/deployment.md) for the full deployment guide including @@ -271,22 +317,22 @@ security hardening, Kubernetes manifests, systemd services, and operational secu ## CLI Reference -All commands accept `--data-dir PATH` to override the default `~/.soosef` directory, +All commands accept `--data-dir PATH` to override the default `~/.fieldwitness` directory, and `--json` for machine-readable output. ``` -soosef [--data-dir PATH] [--json] COMMAND +fieldwitness [--data-dir PATH] [--json] COMMAND ``` ### Core commands | Command | Description | |---|---| -| `soosef init` | Create directory structure, generate identity + channel key, write default config | -| `soosef serve` | Start the web UI (default: `https://127.0.0.1:5000`) | -| `soosef status` | Show instance status: identity, keys, chain, fieldkit, config | +| `fieldwitness init` | Create directory structure, generate identity + channel key, write default config | +| `fieldwitness serve` | Start the web UI (default: `https://127.0.0.1:5000`) | +| `fieldwitness status` | Show instance status: identity, keys, chain, fieldkit, config | -### `soosef serve` options +### `fieldwitness serve` options | Option | Default | Description | |---|---|---| @@ -296,9 +342,70 @@ soosef [--data-dir PATH] [--json] COMMAND | `--debug` | off | Use Flask dev server instead of Waitress | | `--workers` | `4` | Number of Waitress/Gunicorn worker threads | -### Steganography commands (`soosef stego`) +### Attestation commands (`fieldwitness attest`) -Stegasoo uses multi-factor authentication: a **reference photo** (shared image both +```bash +# Attest an image (sign with Ed25519 identity) +fieldwitness attest IMAGE photo.jpg +fieldwitness attest IMAGE photo.jpg --caption "Field report" --location "Istanbul" + +# Batch attest a directory +fieldwitness attest batch ./photos/ --caption "Field report" + +# Verify an image against the attestation log +fieldwitness attest verify photo.jpg + +# View attestation log +fieldwitness attest log --limit 20 +``` + +### Chain commands (`fieldwitness chain`) + +```bash +fieldwitness chain status # Show chain head, length, integrity +fieldwitness chain verify # Verify entire chain integrity (hashes + signatures) +fieldwitness chain show INDEX # Show a specific chain record +fieldwitness chain log --count 20 # Show recent chain entries +fieldwitness chain backfill # Backfill existing attestations into chain + +# Evidence export +fieldwitness chain export --start 0 --end 100 -o chain.zip + +# Selective disclosure (for legal discovery / court orders) +fieldwitness chain disclose -i 5,12,47 -o disclosure.json + +# External timestamp anchoring +fieldwitness chain anchor # Manual anchor (prints hash for tweet/email/blockchain) +fieldwitness chain anchor --tsa https://freetsa.org/tsr # RFC 3161 automated anchor +``` + +### Fieldkit commands (`fieldwitness fieldkit`) + +```bash +fieldwitness fieldkit status # Show fieldkit state +fieldwitness fieldkit checkin # Reset dead man's switch timer +fieldwitness fieldkit check-deadman # Check if deadman timer has expired +fieldwitness fieldkit purge --confirm # Activate killswitch (destroys all data) +fieldwitness fieldkit geofence set --lat 48.8566 --lon 2.3522 --radius 1000 +fieldwitness fieldkit geofence check --lat 48.8600 --lon 2.3500 +fieldwitness fieldkit geofence clear +fieldwitness fieldkit usb snapshot # Snapshot current USB devices as whitelist +fieldwitness fieldkit usb check # Check for unauthorized USB devices +``` + +### Key management commands (`fieldwitness keys`) + +```bash +fieldwitness keys show # Display current key info +fieldwitness keys export -o backup.enc # Export encrypted key bundle +fieldwitness keys import -b backup.enc # Import key bundle +fieldwitness keys rotate-identity # Generate new Ed25519 identity (records rotation in chain) +fieldwitness keys rotate-channel # Generate new channel key +``` + +### Steganography commands (`fieldwitness stego`) + +Stego uses multi-factor authentication: a **reference photo** (shared image both parties have), a **passphrase** (4+ words), and a **PIN** (6-9 digits). All three are required to encode or decode. The passphrase and PIN are prompted interactively (hidden input) if not provided via options. @@ -306,134 +413,73 @@ required to encode or decode. The passphrase and PIN are prompted interactively ```bash # Encode a text message into an image # CARRIER is the image to hide data in, -r is the shared reference photo -soosef stego encode cover.png -r shared_photo.jpg -m "Secret message" +fieldwitness stego encode cover.png -r shared_photo.jpg -m "Secret message" # Passphrase: **** (prompted, hidden) # PIN: **** (prompted, hidden) # -> writes encoded PNG to current directory # Encode with explicit output path -soosef stego encode cover.png -r shared_photo.jpg -m "Secret" -o stego_output.png +fieldwitness stego encode cover.png -r shared_photo.jpg -m "Secret" -o stego_output.png # Encode a file instead of text -soosef stego encode cover.png -r shared_photo.jpg -f document.pdf +fieldwitness stego encode cover.png -r shared_photo.jpg -f document.pdf # Transport-aware encoding (auto-selects DCT/JPEG and resizes for the platform) -soosef stego encode cover.jpg -r shared.jpg -m "Secret" --transport whatsapp -soosef stego encode cover.jpg -r shared.jpg -m "Secret" --transport signal -soosef stego encode cover.jpg -r shared.jpg -m "Secret" --transport telegram -soosef stego encode cover.jpg -r shared.jpg -m "Secret" --transport email -soosef stego encode cover.jpg -r shared.jpg -m "Secret" --transport direct +fieldwitness stego encode cover.jpg -r shared.jpg -m "Secret" --transport whatsapp +fieldwitness stego encode cover.jpg -r shared.jpg -m "Secret" --transport signal +fieldwitness stego encode cover.jpg -r shared.jpg -m "Secret" --transport telegram +fieldwitness stego encode cover.jpg -r shared.jpg -m "Secret" --transport email +fieldwitness stego encode cover.jpg -r shared.jpg -m "Secret" --transport direct # Dry run -- check capacity without encoding -soosef stego encode cover.png -r shared_photo.jpg -m "Secret" --dry-run +fieldwitness stego encode cover.png -r shared_photo.jpg -m "Secret" --dry-run # Decode a message from a stego image (same reference + passphrase + PIN) -soosef stego decode stego_output.png -r shared_photo.jpg +fieldwitness stego decode stego_output.png -r shared_photo.jpg # Passphrase: **** # PIN: **** # -> prints decoded message or saves decoded file # Decode and save file payload to specific path -soosef stego decode stego_output.png -r shared_photo.jpg -o recovered.pdf +fieldwitness stego decode stego_output.png -r shared_photo.jpg -o recovered.pdf # DCT mode for JPEG (survives social media compression) -soosef stego encode cover.jpg -r shared_photo.jpg -m "Secret" --platform telegram +fieldwitness stego encode cover.jpg -r shared_photo.jpg -m "Secret" --platform telegram # Audio steganography -soosef stego audio-encode audio.wav -r shared_photo.jpg -m "Hidden in audio" -soosef stego audio-decode stego.wav -r shared_photo.jpg +fieldwitness stego audio-encode audio.wav -r shared_photo.jpg -m "Hidden in audio" +fieldwitness stego audio-decode stego.wav -r shared_photo.jpg # Generate credentials -soosef stego generate # Generate passphrase + PIN -soosef stego generate --pin-length 8 # Longer PIN +fieldwitness stego generate # Generate passphrase + PIN +fieldwitness stego generate --pin-length 8 # Longer PIN # Channel key management -soosef stego channel status # Show current channel key -soosef stego channel generate # Generate new channel key +fieldwitness stego channel status # Show current channel key +fieldwitness stego channel generate # Generate new channel key # Image info and capacity -soosef stego info cover.png # Image details + LSB/DCT capacity -``` - -### Attestation commands (`soosef attest`) - -```bash -# Attest an image (sign with Ed25519 identity) -soosef attest IMAGE photo.jpg -soosef attest IMAGE photo.jpg --caption "Field report" --location "Istanbul" - -# Batch attest a directory -soosef attest batch ./photos/ --caption "Field report" - -# Verify an image against the attestation log -soosef attest verify photo.jpg - -# View attestation log -soosef attest log --limit 20 -``` - -### Fieldkit commands (`soosef fieldkit`) - -```bash -soosef fieldkit status # Show fieldkit state -soosef fieldkit checkin # Reset dead man's switch timer -soosef fieldkit check-deadman # Check if deadman timer has expired -soosef fieldkit purge --confirm # Activate killswitch (destroys all data) -soosef fieldkit geofence set --lat 48.8566 --lon 2.3522 --radius 1000 -soosef fieldkit geofence check --lat 48.8600 --lon 2.3500 -soosef fieldkit geofence clear -soosef fieldkit usb snapshot # Snapshot current USB devices as whitelist -soosef fieldkit usb check # Check for unauthorized USB devices -``` - -### Key management commands (`soosef keys`) - -```bash -soosef keys show # Display current key info -soosef keys export -o backup.enc # Export encrypted key bundle -soosef keys import -b backup.enc # Import key bundle -soosef keys rotate-identity # Generate new Ed25519 identity (records rotation in chain) -soosef keys rotate-channel # Generate new channel key -``` - -### Chain commands (`soosef chain`) - -```bash -soosef chain status # Show chain head, length, integrity -soosef chain verify # Verify entire chain integrity (hashes + signatures) -soosef chain show INDEX # Show a specific chain record -soosef chain log --count 20 # Show recent chain entries -soosef chain backfill # Backfill existing attestations into chain - -# Evidence export -soosef chain export --start 0 --end 100 -o chain.zip - -# Selective disclosure (for legal discovery / court orders) -soosef chain disclose -i 5,12,47 -o disclosure.json - -# External timestamp anchoring -soosef chain anchor # Manual anchor (prints hash for tweet/email/blockchain) -soosef chain anchor --tsa https://freetsa.org/tsr # RFC 3161 automated anchor +fieldwitness stego info cover.png # Image details + LSB/DCT capacity ``` --- ## Web UI -Start with `soosef serve`. The web UI provides authenticated access to all features +Start with `fieldwitness serve`. The web UI provides authenticated access to all features through Flask blueprints. Served by **Waitress** (production WSGI server) by default. ### Routes | Blueprint | Routes | Description | |---|---|---| -| stego | `/encode`, `/decode`, `/generate` | Steganography operations | | attest | `/attest`, `/verify` | Attestation signing and verification | +| federation | `/federation/*` | Federation peer dashboard, peer add/remove | | fieldkit | `/fieldkit/*` | Killswitch, dead man's switch, status dashboard | | keys | `/keys/*` | Key management, rotation, export/import | | admin | `/admin/*` | User management (multi-user auth via SQLite) | | dropbox | `/dropbox/admin`, `/dropbox/upload/` | Source drop box: token creation (admin), anonymous upload (source), receipt verification | -| federation | `/federation/*` | Federation peer dashboard, peer add/remove | +| stego | `/encode`, `/decode`, `/generate` | Steganography operations | | health | `/health` | Capability reporting endpoint (see API section) | @@ -442,8 +488,8 @@ through Flask blueprints. Served by **Waitress** (production WSGI server) by def ## Configuration -SooSeF loads configuration from `~/.soosef/config.json`. All fields have sensible defaults. -`soosef init` writes the default config file. +FieldWitness loads configuration from `~/.fieldwitness/config.json`. All fields have sensible defaults. +`fieldwitness init` writes the default config file. ### Config fields @@ -457,7 +503,7 @@ SooSeF loads configuration from `~/.soosef/config.json`. All fields have sensibl | `session_timeout_minutes` | int | `15` | Session expiry | | `login_lockout_attempts` | int | `5` | Failed logins before lockout | | `login_lockout_minutes` | int | `15` | Lockout duration | -| `default_embed_mode` | string | `auto` | Stegasoo encoding mode | +| `default_embed_mode` | string | `auto` | Stego encoding mode | | `killswitch_enabled` | bool | `false` | Enable killswitch functionality | | `deadman_enabled` | bool | `false` | Enable dead man's switch | | `deadman_interval_hours` | int | `24` | Check-in interval | @@ -468,7 +514,7 @@ SooSeF loads configuration from `~/.soosef/config.json`. All fields have sensibl | `chain_enabled` | bool | `true` | Enable attestation hash chain | | `chain_auto_wrap` | bool | `true` | Auto-wrap attestations in chain records | | `backup_reminder_days` | int | `7` | Days before backup reminder | -| `cover_name` | string | `""` | If set, used for SSL cert CN instead of "SooSeF Local" (cover/duress mode) | +| `cover_name` | string | `""` | If set, used for SSL cert CN instead of "FieldWitness Local" (cover/duress mode) | | `gpio_killswitch_pin` | int | `17` | Raspberry Pi GPIO pin for hardware killswitch | | `gpio_killswitch_hold_seconds` | float | `5.0` | Hold duration to trigger hardware killswitch | @@ -476,7 +522,7 @@ SooSeF loads configuration from `~/.soosef/config.json`. All fields have sensibl | Variable | Description | |---|---| -| `SOOSEF_DATA_DIR` | Override the data directory (default: `~/.soosef`). Enables portable USB mode and cover/duress directory naming | +| `FIELDWITNESS_DATA_DIR` | Override the data directory (default: `~/.fieldwitness`). Enables portable USB mode and cover/duress directory naming | --- @@ -485,19 +531,16 @@ SooSeF loads configuration from `~/.soosef/config.json`. All fields have sensibl ### Source layout ``` -src/soosef/ +src/fieldwitness/ __init__.py Package init, __version__ - cli.py Click CLI (entry point: soosef) + cli.py Click CLI (entry point: fieldwitness) paths.py All path constants (lazy resolution from BASE_DIR) config.py Unified config loader (dataclass + JSON) - exceptions.py SoosefError base exception + exceptions.py FieldWitnessError base exception metadata.py Extract-then-strip EXIF pipeline evidence.py Self-contained evidence package export archive.py Cold archive for long-term preservation (OAIS-aligned) - stegasoo/ Steganography engine (subpackage) - encode.py Transport-aware encoding (--transport flag) - carrier_tracker.py Carrier reuse tracking and warnings - verisoo/ Attestation engine (subpackage) + attest/ Attestation engine (subpackage) models.py ImageHashes (images + arbitrary files), AttestationRecord keystore/ manager.py Key material management (channel + identity + trust store + backup) @@ -513,6 +556,9 @@ src/soosef/ tamper.py File integrity monitoring usb_monitor.py USB device whitelist (Linux/pyudev) geofence.py GPS boundary enforcement (gpsd integration) + stego/ Steganography engine (subpackage) + encode.py Transport-aware encoding (--transport flag) + carrier_tracker.py Carrier reuse tracking and warnings frontends/web/ app.py Flask app factory (create_app()) @@ -521,13 +567,13 @@ frontends/web/ subprocess_stego.py Crash-safe subprocess isolation ssl_utils.py Self-signed HTTPS cert generation blueprints/ - stego.py /encode, /decode, /generate attest.py /attest, /verify + federation.py /federation/* (peer dashboard) fieldkit.py /fieldkit/* keys.py /keys/* admin.py /admin/* dropbox.py /dropbox/* (source drop box) - federation.py /federation/* (peer dashboard) + stego.py /encode, /decode, /generate deploy/ Deployment artifacts docker/ Dockerfile (multi-stage: builder, relay, server) + compose @@ -536,18 +582,18 @@ deploy/ Deployment artifacts config-presets/ Threat level presets (low/medium/high/critical) ``` -### Data directory (`~/.soosef/`) +### Data directory (`~/.fieldwitness/`) ``` -~/.soosef/ +~/.fieldwitness/ config.json Unified configuration audit.jsonl Append-only audit trail carrier_history.json Carrier reuse tracking database identity/ Ed25519 keypair (private.pem, public.pem, identity.meta.json) - stegasoo/ Channel key (channel.key) - attestations/ Verisoo attestation store (log.bin, index/, peers.json) + stego/ Channel key (channel.key) + attestations/ Attest attestation store (log.bin, index/, peers.json) chain/ Hash chain (chain.bin, state.cbor, anchors/) - auth/ Web UI auth database (soosef.db, dropbox.db) + auth/ Web UI auth database (fieldwitness.db, dropbox.db) certs/ Self-signed TLS certificates fieldkit/ Fieldkit state (deadman.json, tamper/, usb/, geofence.json) temp/ Ephemeral file storage (dropbox uploads) @@ -562,15 +608,15 @@ Sensitive directories (`identity/`, `auth/`, `certs/`, and the root) are created ## Security Model -**Two key domains, never merged.** Stegasoo uses AES-256-GCM with keys derived via -Argon2id from user-supplied factors. Verisoo uses Ed25519 for signing. These serve +**Two key domains, never merged.** Stego uses AES-256-GCM with keys derived via +Argon2id from user-supplied factors. Attest uses Ed25519 for signing. These serve different security purposes and are kept strictly separate. -**Killswitch priority.** The killswitch destroys all data under `~/.soosef/`, including +**Killswitch priority.** The killswitch destroys all data under `~/.fieldwitness/`, including the audit log. This is intentional -- in a field compromise scenario, data destruction takes precedence over audit trail preservation. The deep forensic scrub extends beyond the data directory to remove Python bytecache, pip metadata, pip download cache, shell -history entries, and the soosef package itself. +history entries, and the fieldwitness package itself. **Offline-first.** All static assets are vendored (no CDN calls). Pip wheels can be bundled for fully airgapped installation. No network access is required for any core @@ -598,16 +644,30 @@ specific records without accessing the full chain. ## Cross-Domain Applications -While SooSeF was designed for journalist and NGO field security, the attestation chain, +While FieldWitness was designed for journalist and NGO field security, the attestation chain, federation, and evidence packaging capabilities apply to a range of domains: -- **Human rights documentation** -- field workers attest photos and videos of incidents with GPS and timestamps, federate evidence to international partners, and produce court-ready evidence packages -- **Research integrity** -- researchers attest datasets (CSV, sensor readings) at collection time, creating a tamper-evident chain of custody. `ImageHashes.from_file()` supports arbitrary file types via SHA-256 -- **Election monitoring** -- observers attest ballot images and tally sheets with location metadata, anchor the chain to an RFC 3161 TSA for independent time proof, and use selective disclosure for audit requests -- **Supply chain verification** -- attest inspection photos, sensor data, and certificates of origin at each stage. Federation enables multi-party chains across organizations -- **Art authentication** -- attest high-resolution photographs of artworks with device and location metadata, creating provenance records that survive format conversion via perceptual hashing -- **Corporate whistleblowing** -- the source drop box accepts anonymous uploads with client-side hashing. Cover mode (`cover_name`, `SOOSEF_DATA_DIR`) disguises the installation. The killswitch provides emergency destruction if the instance is compromised -- **Environmental monitoring** -- attest sensor data, satellite imagery, and field photographs. Cold archives with `ALGORITHMS.txt` ensure evidence remains verifiable decades later +- **Human rights documentation** -- field workers attest photos and videos of incidents + with GPS and timestamps, federate evidence to international partners, and produce + court-ready evidence packages +- **Research integrity** -- researchers attest datasets (CSV, sensor readings) at + collection time, creating a tamper-evident chain of custody. `ImageHashes.from_file()` + supports arbitrary file types via SHA-256 +- **Election monitoring** -- observers attest ballot images and tally sheets with location + metadata, anchor the chain to an RFC 3161 TSA for independent time proof, and use + selective disclosure for audit requests +- **Supply chain verification** -- attest inspection photos, sensor data, and certificates + of origin at each stage. Federation enables multi-party chains across organizations +- **Art authentication** -- attest high-resolution photographs of artworks with device and + location metadata, creating provenance records that survive format conversion via + perceptual hashing +- **Corporate whistleblowing** -- the source drop box accepts anonymous uploads with + client-side hashing. Cover mode (`cover_name`, `FIELDWITNESS_DATA_DIR`) disguises the + installation. The killswitch provides emergency destruction if the instance is + compromised +- **Environmental monitoring** -- attest sensor data, satellite imagery, and field + photographs. Cold archives with `ALGORITHMS.txt` ensure evidence remains verifiable + decades later --- @@ -632,10 +692,10 @@ Useful for monitoring and for clients to discover which extras are installed. Install the `api` extra for a standalone FastAPI REST interface: ```bash -pip install "soosef[api]" +pip install "fieldwitness[api]" ``` -This provides `soosef.api` with a FastAPI application served by uvicorn, suitable for +This provides `fieldwitness.api` with a FastAPI application served by uvicorn, suitable for programmatic integration. --- @@ -645,8 +705,8 @@ programmatic integration. ### Setup ```bash -git clone https://github.com/alee/soosef.git -cd soosef +git clone https://github.com/alee/fieldwitness.git +cd fieldwitness pip install -e ".[dev]" ``` @@ -680,6 +740,7 @@ Python 3.11, 3.12, 3.13, and 3.14. | [docs/federation.md](docs/federation.md) | System administrators | Gossip protocol, peer setup, offline bundles, federation API | | [docs/evidence-guide.md](docs/evidence-guide.md) | Investigators, legal teams | Evidence packages, cold archives, selective disclosure, anchoring | | [docs/source-dropbox.md](docs/source-dropbox.md) | Administrators | Source drop box setup, EXIF pipeline, receipt codes | +| [docs/security/threat-model.md](docs/security/threat-model.md) | Security reviewers, contributors | Threat model, adversary model, trust boundaries, cryptographic primitives | | [docs/training/reporter-quickstart.md](docs/training/reporter-quickstart.md) | Field reporters | One-page quick-start card for Tier 1 USB users | | [docs/training/emergency-card.md](docs/training/emergency-card.md) | All users | Laminated wallet card: emergency destruction, dead man's switch | | [docs/training/admin-reference.md](docs/training/admin-reference.md) | Administrators | CLI cheat sheet, hardening checklist, troubleshooting | @@ -697,4 +758,4 @@ Architecture documents (design-level, for contributors): ## License -MIT License. See [LICENSE](LICENSE) for details. +GPL-3.0 License. See [LICENSE](LICENSE) for details. diff --git a/deploy/config-presets/README.md b/deploy/config-presets/README.md index 24f9c67..ec9fd99 100644 --- a/deploy/config-presets/README.md +++ b/deploy/config-presets/README.md @@ -1,7 +1,7 @@ -# SooSeF Threat Level Configuration Presets +# FieldWitness Threat Level Configuration Presets Select a preset based on your operational environment. Copy the appropriate -JSON file to `~/.soosef/config.json` (or let the setup wizard choose one). +JSON file to `~/.fwmetadata/config.json` (or let the setup wizard choose one). ## Presets @@ -40,7 +40,7 @@ Specific journalist or org targeted by state actor (Pegasus-level). ```bash # Copy preset to config location -cp deploy/config-presets/high-threat.json ~/.soosef/config.json +cp deploy/config-presets/high-threat.json ~/.fwmetadata/config.json -# Or via CLI (future: soosef init --threat-level high) +# Or via CLI (future: fieldwitness init --threat-level high) ``` diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 52ed9f1..2a1a195 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -1,13 +1,13 @@ -# SooSeF Federation Server +# FieldWitness Federation Server # Multi-stage build for minimal image size. # # Tier 2: Org server (full features — web UI, attestation, federation, stego) -# docker build -t soosef-server . -# docker run -v soosef-data:/data -p 5000:5000 -p 8000:8000 soosef-server +# docker build -t fieldwitness-server . +# docker run -v fieldwitness-data:/data -p 5000:5000 -p 8000:8000 fieldwitness-server # # Tier 3: Federation relay (attestation + federation only, no stego, no web UI) -# docker build --target relay -t soosef-relay . -# docker run -v relay-data:/data -p 8000:8000 soosef-relay +# docker build --target relay -t fieldwitness-relay . +# docker run -v relay-data:/data -p 8000:8000 fieldwitness-relay # === Stage 1: Build dependencies === FROM python:3.12-slim-bookworm AS builder @@ -22,8 +22,8 @@ WORKDIR /build COPY . . # Install into a virtual environment for clean copying -RUN python -m venv /opt/soosef-env \ - && /opt/soosef-env/bin/pip install --no-cache-dir \ +RUN python -m venv /opt/fieldwitness-env \ + && /opt/fieldwitness-env/bin/pip install --no-cache-dir \ ".[web,cli,attest,stego-dct,api,federation]" # === Stage 2: Federation relay (minimal) === @@ -32,21 +32,21 @@ FROM python:3.12-slim-bookworm AS relay RUN apt-get update && apt-get install -y --no-install-recommends \ libjpeg62-turbo libopenblas0 \ && rm -rf /var/lib/apt/lists/* \ - && useradd -m -s /bin/bash soosef + && useradd -m -s /bin/bash fieldwitness -COPY --from=builder /opt/soosef-env /opt/soosef-env +COPY --from=builder /opt/fieldwitness-env /opt/fieldwitness-env -ENV PATH="/opt/soosef-env/bin:$PATH" \ - SOOSEF_DATA_DIR=/data \ +ENV PATH="/opt/fieldwitness-env/bin:$PATH" \ + FIELDWITNESS_DATA_DIR=/data \ PYTHONUNBUFFERED=1 VOLUME /data EXPOSE 8000 -USER soosef +USER fieldwitness -# Federation relay: only the verisoo API with federation endpoints -CMD ["uvicorn", "soosef.verisoo.api:app", "--host", "0.0.0.0", "--port", "8000"] +# Federation relay: only the attest API with federation endpoints +CMD ["uvicorn", "fieldwitness.attest.api:app", "--host", "0.0.0.0", "--port", "8000"] HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" @@ -57,25 +57,25 @@ FROM python:3.12-slim-bookworm AS server RUN apt-get update && apt-get install -y --no-install-recommends \ libjpeg62-turbo libopenblas0 \ && rm -rf /var/lib/apt/lists/* \ - && useradd -m -s /bin/bash soosef + && useradd -m -s /bin/bash fieldwitness -COPY --from=builder /opt/soosef-env /opt/soosef-env +COPY --from=builder /opt/fieldwitness-env /opt/fieldwitness-env # Copy frontend templates and static assets -COPY frontends/ /opt/soosef-env/lib/python3.12/site-packages/frontends/ +COPY frontends/ /opt/fieldwitness-env/lib/python3.12/site-packages/frontends/ -ENV PATH="/opt/soosef-env/bin:$PATH" \ - SOOSEF_DATA_DIR=/data \ +ENV PATH="/opt/fieldwitness-env/bin:$PATH" \ + FIELDWITNESS_DATA_DIR=/data \ PYTHONUNBUFFERED=1 VOLUME /data EXPOSE 5000 8000 -USER soosef +USER fieldwitness # Init on first run, then start web UI (HTTPS by default with self-signed cert). # Use --no-https explicitly if running behind a TLS-terminating reverse proxy. -CMD ["sh", "-c", "soosef init 2>/dev/null; soosef serve --host 0.0.0.0"] +CMD ["sh", "-c", "fieldwitness init 2>/dev/null; fieldwitness serve --host 0.0.0.0"] HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')" diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index e847f3c..4e6ac8b 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -1,4 +1,4 @@ -# SooSeF Docker Compose — Three-Tier Deployment +# FieldWitness Docker Compose — Three-Tier Deployment # # Tier 2 (Org Server): Full web UI + attestation + federation # Tier 3 (Federation Relay): Lightweight attestation API only @@ -10,7 +10,7 @@ services: # === Tier 2: Organizational Server === - # Full SooSeF instance with web UI, stego, attestation, federation. + # Full FieldWitness instance with web UI, stego, attestation, federation. # Deploy on a mini PC in the newsroom or a trusted VPS. server: build: @@ -23,8 +23,8 @@ services: volumes: - server-data:/data environment: - - SOOSEF_DATA_DIR=/data - - VERISOO_GOSSIP_INTERVAL=60 + - FIELDWITNESS_DATA_DIR=/data + - FIELDWITNESS_GOSSIP_INTERVAL=60 restart: unless-stopped # === Tier 3: Federation Relay === @@ -41,7 +41,7 @@ services: volumes: - relay-data:/data environment: - - SOOSEF_DATA_DIR=/data + - FIELDWITNESS_DATA_DIR=/data restart: unless-stopped volumes: diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index ffb8812..71264be 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -1,4 +1,4 @@ -# SooSeF Kubernetes Deployment +# FieldWitness Kubernetes Deployment ## Architecture @@ -29,8 +29,8 @@ ```bash # Build images -docker build -t soosef-server --target server -f deploy/docker/Dockerfile . -docker build -t soosef-relay --target relay -f deploy/docker/Dockerfile . +docker build -t fieldwitness-server --target server -f deploy/docker/Dockerfile . +docker build -t fieldwitness-relay --target relay -f deploy/docker/Dockerfile . # Deploy to Kubernetes kubectl apply -f deploy/kubernetes/namespace.yaml @@ -41,7 +41,7 @@ kubectl apply -f deploy/kubernetes/relay-deployment.yaml ## Notes - **Single writer**: Both deployments use `replicas: 1` with `Recreate` strategy. - SooSeF uses SQLite and append-only binary logs that require single-writer access. + FieldWitness uses SQLite and append-only binary logs that require single-writer access. Do not scale horizontally. - **PVCs**: Both deployments require persistent volumes. The server needs 10Gi, the relay needs 5Gi. Adjust based on expected attestation volume. diff --git a/deploy/kubernetes/namespace.yaml b/deploy/kubernetes/namespace.yaml index 2a4e05f..2516027 100644 --- a/deploy/kubernetes/namespace.yaml +++ b/deploy/kubernetes/namespace.yaml @@ -1,6 +1,6 @@ apiVersion: v1 kind: Namespace metadata: - name: soosef + name: fieldwitness labels: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness diff --git a/deploy/kubernetes/relay-deployment.yaml b/deploy/kubernetes/relay-deployment.yaml index f51d974..ca7ee8a 100644 --- a/deploy/kubernetes/relay-deployment.yaml +++ b/deploy/kubernetes/relay-deployment.yaml @@ -1,13 +1,13 @@ -# SooSeF Federation Relay — Lightweight attestation sync relay. +# FieldWitness Federation Relay — Lightweight attestation sync relay. # Deploy on a VPS in a favorable jurisdiction for geographic redundancy. # Stores only attestation records — zero knowledge of encryption keys. apiVersion: apps/v1 kind: Deployment metadata: - name: soosef-relay - namespace: soosef + name: fieldwitness-relay + namespace: fieldwitness labels: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: relay spec: replicas: 1 @@ -15,12 +15,12 @@ spec: type: Recreate selector: matchLabels: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: relay template: metadata: labels: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: relay spec: securityContext: @@ -29,12 +29,12 @@ spec: fsGroup: 1000 containers: - name: relay - image: soosef-relay:latest + image: fieldwitness-relay:latest ports: - containerPort: 8000 name: federation env: - - name: SOOSEF_DATA_DIR + - name: FIELDWITNESS_DATA_DIR value: /data volumeMounts: - name: data @@ -61,7 +61,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: name: relay-data - namespace: soosef + namespace: fieldwitness spec: accessModes: - ReadWriteOnce @@ -72,11 +72,11 @@ spec: apiVersion: v1 kind: Service metadata: - name: soosef-relay - namespace: soosef + name: fieldwitness-relay + namespace: fieldwitness spec: selector: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: relay ports: - name: federation diff --git a/deploy/kubernetes/server-deployment.yaml b/deploy/kubernetes/server-deployment.yaml index 69d16b3..c0127bb 100644 --- a/deploy/kubernetes/server-deployment.yaml +++ b/deploy/kubernetes/server-deployment.yaml @@ -1,12 +1,12 @@ -# SooSeF Org Server — Full deployment with persistent storage. +# FieldWitness Org Server — Full deployment with persistent storage. # For newsroom or trusted infrastructure deployment. apiVersion: apps/v1 kind: Deployment metadata: - name: soosef-server - namespace: soosef + name: fieldwitness-server + namespace: fieldwitness labels: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: server spec: replicas: 1 # Single writer — do not scale horizontally @@ -14,12 +14,12 @@ spec: type: Recreate # Not RollingUpdate — SQLite + append-only logs need single writer selector: matchLabels: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: server template: metadata: labels: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: server spec: securityContext: @@ -27,17 +27,17 @@ spec: runAsGroup: 1000 fsGroup: 1000 containers: - - name: soosef - image: soosef-server:latest + - name: fieldwitness + image: fieldwitness-server:latest ports: - containerPort: 5000 name: web - containerPort: 8000 name: federation env: - - name: SOOSEF_DATA_DIR + - name: FIELDWITNESS_DATA_DIR value: /data - - name: VERISOO_GOSSIP_INTERVAL + - name: FIELDWITNESS_GOSSIP_INTERVAL value: "60" volumeMounts: - name: data @@ -64,13 +64,13 @@ spec: volumes: - name: data persistentVolumeClaim: - claimName: soosef-data + claimName: fieldwitness-data --- apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: soosef-data - namespace: soosef + name: fieldwitness-data + namespace: fieldwitness spec: accessModes: - ReadWriteOnce @@ -81,11 +81,11 @@ spec: apiVersion: v1 kind: Service metadata: - name: soosef-server - namespace: soosef + name: fieldwitness-server + namespace: fieldwitness spec: selector: - app.kubernetes.io/name: soosef + app.kubernetes.io/name: fieldwitness app.kubernetes.io/component: server ports: - name: web diff --git a/deploy/live-usb/build.sh b/deploy/live-usb/build.sh index 548bcbf..4bd8e58 100755 --- a/deploy/live-usb/build.sh +++ b/deploy/live-usb/build.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Build a bootable Debian Live USB image with SooSeF pre-installed. +# Build a bootable Debian Live USB image with FieldWitness pre-installed. # # Prerequisites: # apt install live-build @@ -12,10 +12,10 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -SOOSEF_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +FIELDWITNESS_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" -echo "=== SooSeF Live USB Image Builder ===" -echo "Source: $SOOSEF_ROOT" +echo "=== FieldWitness Live USB Image Builder ===" +echo "Source: $FIELDWITNESS_ROOT" echo cd "$SCRIPT_DIR" diff --git a/deploy/live-usb/config/hooks/live/0100-install-soosef.hook.chroot b/deploy/live-usb/config/hooks/live/0100-install-soosef.hook.chroot index c0cdf20..b62fa3f 100755 --- a/deploy/live-usb/config/hooks/live/0100-install-soosef.hook.chroot +++ b/deploy/live-usb/config/hooks/live/0100-install-soosef.hook.chroot @@ -1,26 +1,26 @@ #!/bin/bash -# Install SooSeF and all dependencies into the live image. +# Install FieldWitness and all dependencies into the live image. # This runs inside the chroot during image build. set -euo pipefail -echo "=== Installing SooSeF ===" +echo "=== Installing FieldWitness ===" -# Create soosef user -useradd -m -s /bin/bash -G sudo soosef -echo "soosef:soosef" | chpasswd +# Create fieldwitness user +useradd -m -s /bin/bash -G sudo fieldwitness +echo "fieldwitness:fieldwitness" | chpasswd # Create virtual environment -python3 -m venv /opt/soosef-env -source /opt/soosef-env/bin/activate +python3 -m venv /opt/fieldwitness-env +source /opt/fieldwitness-env/bin/activate -# Install soosef with all extras (pre-built wheels from PyPI) -pip install --no-cache-dir "soosef[web,cli,attest,stego-dct,stego-audio,fieldkit]" +# Install fieldwitness with all extras (pre-built wheels from PyPI) +pip install --no-cache-dir "fieldwitness[web,cli,attest,stego-dct,stego-audio,fieldkit]" # Verify installation -python3 -c "import soosef; print(f'SooSeF {soosef.__version__} installed')" -python3 -c "from soosef.stegasoo import encode; print('stegasoo OK')" -python3 -c "from soosef.verisoo import Attestation; print('verisoo OK')" +python3 -c "import fieldwitness; print(f'FieldWitness {fieldwitness.__version__} installed')" +python3 -c "from fieldwitness.stego import encode; print('stego OK')" +python3 -c "from fieldwitness.attest import Attestation; print('attest OK')" deactivate -echo "=== SooSeF installation complete ===" +echo "=== FieldWitness installation complete ===" diff --git a/deploy/live-usb/config/hooks/live/0200-harden.hook.chroot b/deploy/live-usb/config/hooks/live/0200-harden.hook.chroot index 6e5e224..49b928c 100755 --- a/deploy/live-usb/config/hooks/live/0200-harden.hook.chroot +++ b/deploy/live-usb/config/hooks/live/0200-harden.hook.chroot @@ -6,17 +6,17 @@ echo "=== Applying security hardening ===" # Disable core dumps (Python doesn't zero memory — core dumps leak keys) echo "* hard core 0" >> /etc/security/limits.conf -echo "fs.suid_dumpable = 0" >> /etc/sysctl.d/99-soosef.conf -echo "kernel.core_pattern=|/bin/false" >> /etc/sysctl.d/99-soosef.conf +echo "fs.suid_dumpable = 0" >> /etc/sysctl.d/99-fieldwitness.conf +echo "kernel.core_pattern=|/bin/false" >> /etc/sysctl.d/99-fieldwitness.conf # Disable swap (keys persist in swap pages) systemctl mask swap.target || true -echo "vm.swappiness = 0" >> /etc/sysctl.d/99-soosef.conf +echo "vm.swappiness = 0" >> /etc/sysctl.d/99-fieldwitness.conf # Enable UFW with deny-all + allow web UI ufw default deny incoming ufw default allow outgoing -ufw allow 5000/tcp comment "SooSeF Web UI" +ufw allow 5000/tcp comment "FieldWitness Web UI" ufw allow 22/tcp comment "SSH" ufw --force enable || true @@ -25,14 +25,14 @@ systemctl disable bluetooth.service 2>/dev/null || true systemctl disable avahi-daemon.service 2>/dev/null || true systemctl disable cups.service 2>/dev/null || true -# Enable SooSeF service +# Enable FieldWitness service systemctl enable soosef.service # Auto-login to openbox (so the browser opens without login prompt) mkdir -p /etc/lightdm/lightdm.conf.d cat > /etc/lightdm/lightdm.conf.d/50-autologin.conf << 'EOF' [Seat:*] -autologin-user=soosef +autologin-user=fieldwitness autologin-user-timeout=0 EOF diff --git a/deploy/live-usb/config/includes.chroot/etc/systemd/system/soosef.service b/deploy/live-usb/config/includes.chroot/etc/systemd/system/soosef.service index 763c346..8d4968f 100644 --- a/deploy/live-usb/config/includes.chroot/etc/systemd/system/soosef.service +++ b/deploy/live-usb/config/includes.chroot/etc/systemd/system/soosef.service @@ -1,17 +1,17 @@ [Unit] -Description=SooSeF Security Fieldkit +Description=FieldWitness After=network-online.target Wants=network-online.target [Service] Type=simple -User=soosef -Group=soosef -WorkingDirectory=/home/soosef -Environment=PATH=/opt/soosef-env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -Environment=SOOSEF_DATA_DIR=/home/soosef/.soosef -ExecStartPre=/opt/soosef-env/bin/soosef init --no-identity --no-channel -ExecStart=/opt/soosef-env/bin/soosef serve --host 0.0.0.0 --no-https +User=fieldwitness +Group=fieldwitness +WorkingDirectory=/home/fieldwitness +Environment=PATH=/opt/fieldwitness-env/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +Environment=FIELDWITNESS_DATA_DIR=/home/fieldwitness/.fwmetadata +ExecStartPre=/opt/fieldwitness-env/bin/fieldwitness init --no-identity --no-channel +ExecStart=/opt/fieldwitness-env/bin/fieldwitness serve --host 0.0.0.0 --no-https Restart=on-failure RestartSec=5 @@ -19,7 +19,7 @@ RestartSec=5 NoNewPrivileges=yes ProtectSystem=strict ProtectHome=read-only -ReadWritePaths=/home/soosef/.soosef +ReadWritePaths=/home/fieldwitness/.fwmetadata PrivateTmp=yes ProtectKernelTunables=yes ProtectControlGroups=yes diff --git a/deploy/live-usb/config/includes.chroot/etc/xdg/openbox/autostart b/deploy/live-usb/config/includes.chroot/etc/xdg/openbox/autostart index a7ef1e4..87dca21 100644 --- a/deploy/live-usb/config/includes.chroot/etc/xdg/openbox/autostart +++ b/deploy/live-usb/config/includes.chroot/etc/xdg/openbox/autostart @@ -1,4 +1,4 @@ -# SooSeF Live USB — auto-open web UI in Firefox -# Wait for the SooSeF server to start, then open the browser +# FieldWitness Live USB — auto-open web UI in Firefox +# Wait for the FieldWitness server to start, then open the browser sleep 5 firefox-esr --kiosk http://127.0.0.1:5000 & diff --git a/deploy/live-usb/config/package-lists/soosef.list.chroot b/deploy/live-usb/config/package-lists/soosef.list.chroot index 7f62ddf..628bee3 100644 --- a/deploy/live-usb/config/package-lists/soosef.list.chroot +++ b/deploy/live-usb/config/package-lists/soosef.list.chroot @@ -12,7 +12,7 @@ libssl-dev gfortran libopenblas-dev -## SooSeF runtime dependencies +## FieldWitness runtime dependencies gpsd gpsd-clients cryptsetup diff --git a/docker/Dockerfile b/docker/Dockerfile index e835146..6bd1c79 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,15 +1,9 @@ -# SooSeF Docker Image -# -# Requires stegasoo and verisoo source directories alongside soosef: -# Sources/ -# ├── stegasoo/ -# ├── verisoo/ -# └── soosef/ ← build context is parent (Sources/) +# FieldWitness Docker Image # # Build: -# docker build -t soosef -f soosef/docker/Dockerfile . +# docker build -t fieldwitness -f docker/Dockerfile . # -# Or use docker-compose from soosef/docker/: +# Or use docker-compose from docker/: # docker compose up FROM python:3.12-slim @@ -33,35 +27,21 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ WORKDIR /app -# ── Install stegasoo ───────────────────────────────────────────── -COPY stegasoo/pyproject.toml stegasoo/pyproject.toml -COPY stegasoo/README.md stegasoo/README.md -COPY stegasoo/src/ stegasoo/src/ -COPY stegasoo/data/ stegasoo/data/ -COPY stegasoo/frontends/ stegasoo/frontends/ -RUN pip install --no-cache-dir /app/stegasoo[web,dct,audio,cli] - -# ── Install verisoo ────────────────────────────────────────────── -COPY verisoo/pyproject.toml verisoo/pyproject.toml -COPY verisoo/README.md verisoo/README.md -COPY verisoo/src/ verisoo/src/ -RUN pip install --no-cache-dir /app/verisoo[cli] - -# ── Install soosef ─────────────────────────────────────────────── -COPY soosef/pyproject.toml soosef/pyproject.toml -COPY soosef/README.md soosef/README.md -COPY soosef/src/ soosef/src/ -COPY soosef/frontends/ soosef/frontends/ -RUN pip install --no-cache-dir /app/soosef[web,cli] +# ── Install fieldwitness ───────────────────────────────────────── +COPY pyproject.toml pyproject.toml +COPY README.md README.md +COPY src/ src/ +COPY frontends/ frontends/ +RUN pip install --no-cache-dir /app[web,cli] # ── Runtime setup ──────────────────────────────────────────────── -RUN mkdir -p /root/.soosef +RUN mkdir -p /root/.fwmetadata -COPY soosef/docker/entrypoint.sh /app/entrypoint.sh +COPY docker/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh -ENV SOOSEF_DATA_DIR=/root/.soosef -WORKDIR /app/soosef +ENV FIELDWITNESS_DATA_DIR=/root/.fwmetadata +WORKDIR /app EXPOSE 35811 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index bab486a..bf3c3e6 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,19 +1,19 @@ services: - soosef: + fieldwitness: build: - context: ../.. # Sources/ directory (contains stegasoo/, verisoo/, soosef/) - dockerfile: soosef/docker/Dockerfile - container_name: soosef + context: ../.. # Sources/ directory (contains stego/, attest/, fieldwitness/) + dockerfile: fieldwitness/docker/Dockerfile + container_name: fieldwitness ports: - "35811:35811" environment: - SOOSEF_DATA_DIR: /root/.soosef - SOOSEF_PORT: "35811" - SOOSEF_WORKERS: "2" - SOOSEF_HTTPS_ENABLED: "${SOOSEF_HTTPS_ENABLED:-false}" - STEGASOO_CHANNEL_KEY: "${STEGASOO_CHANNEL_KEY:-}" + FIELDWITNESS_DATA_DIR: /root/.fwmetadata + FIELDWITNESS_PORT: "35811" + FIELDWITNESS_WORKERS: "2" + FIELDWITNESS_HTTPS_ENABLED: "${FIELDWITNESS_HTTPS_ENABLED:-false}" + FIELDWITNESS_CHANNEL_KEY: "${FIELDWITNESS_CHANNEL_KEY:-}" volumes: - - soosef-data:/root/.soosef + - fieldwitness-data:/root/.fwmetadata restart: unless-stopped healthcheck: test: ["CMD", "curl", "-fs", "--max-time", "3", "http://localhost:35811/"] @@ -29,5 +29,5 @@ services: memory: 512M volumes: - soosef-data: + fieldwitness-data: driver: local diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 73d432f..65cfd81 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -2,24 +2,24 @@ set -e # Initialize if needed (generates identity + channel key + config) -if [ ! -f "$SOOSEF_DATA_DIR/config.json" ]; then - echo "First run — initializing SooSeF..." - soosef init +if [ ! -f "$FIELDWITNESS_DATA_DIR/config.json" ]; then + echo "First run — initializing FieldWitness..." + fieldwitness init echo "Initialization complete." fi # Determine HTTPS mode HTTPS_FLAG="" -if [ "${SOOSEF_HTTPS_ENABLED:-true}" = "false" ]; then +if [ "${FIELDWITNESS_HTTPS_ENABLED:-true}" = "false" ]; then HTTPS_FLAG="--no-https" fi -echo "Starting SooSeF on port ${SOOSEF_PORT:-35811}..." +echo "Starting FieldWitness on port ${FIELDWITNESS_PORT:-35811}..." # Run with gunicorn for production exec gunicorn \ - --bind "0.0.0.0:${SOOSEF_PORT:-35811}" \ - --workers "${SOOSEF_WORKERS:-2}" \ + --bind "0.0.0.0:${FIELDWITNESS_PORT:-35811}" \ + --workers "${FIELDWITNESS_WORKERS:-2}" \ --timeout 180 \ --access-logfile - \ --error-logfile - \ diff --git a/docs/architecture/chain-format.md b/docs/architecture/chain-format.md index f939de9..43ac3a9 100644 --- a/docs/architecture/chain-format.md +++ b/docs/architecture/chain-format.md @@ -10,7 +10,7 @@ The attestation chain is an append-only sequence of signed records stored locall offline device. Each record includes a hash of the previous record, forming a tamper-evident chain analogous to git commits or blockchain blocks. -The chain wraps existing Verisoo attestation records. A Verisoo record's serialized bytes +The chain wraps existing Attest attestation records. A Attest record's serialized bytes become the input to `content_hash`, preserving the original attestation while adding ordering, entropy witnesses, and chain integrity guarantees. @@ -24,8 +24,8 @@ ordering, entropy witnesses, and chain integrity guarantees. | `record_id` | 1 | byte string | 16 bytes | UUID v7 (RFC 9562). Time-ordered unique identifier. | | `chain_index` | 2 | unsigned int | 8 bytes max | Monotonically increasing, 0-based. Genesis record is index 0. | | `prev_hash` | 3 | byte string | 32 bytes | SHA-256 of `canonical_bytes(previous_record)`. Genesis: `0x00 * 32`. | -| `content_hash` | 4 | byte string | 32 bytes | SHA-256 of the wrapped content (e.g., Verisoo record bytes). | -| `content_type` | 5 | text string | variable | MIME-like type identifier. `"verisoo/attestation-v1"` for Verisoo records. | +| `content_hash` | 4 | byte string | 32 bytes | SHA-256 of the wrapped content (e.g., Attest record bytes). | +| `content_type` | 5 | text string | variable | MIME-like type identifier. `"attest/attestation-v1"` for Attest records. | | `metadata` | 6 | CBOR map | variable | Extensible key-value map. See §2.1. | | `claimed_ts` | 7 | integer | 8 bytes max | Unix timestamp in microseconds (µs). Signed integer to handle pre-epoch dates. | | `entropy_witnesses` | 8 | CBOR map | variable | System entropy snapshot. See §3. | @@ -41,7 +41,7 @@ The `metadata` field is an open CBOR map with text string keys. Defined keys: | `"backfilled"` | bool | `true` if this record was created by the backfill migration | | `"caption"` | text | Human-readable description of the attested content | | `"location"` | text | Location name associated with the attestation | -| `"original_ts"` | integer | Original Verisoo timestamp (µs) if different from `claimed_ts` | +| `"original_ts"` | integer | Original Attest timestamp (µs) if different from `claimed_ts` | | `"tags"` | array of text | User-defined classification tags | Applications may add custom keys. Unknown keys must be preserved during serialization. @@ -214,24 +214,24 @@ This file is a performance optimization — the canonical state is always deriva ### 6.3 File Locations ``` -~/.soosef/chain/ +~/.fwmetadata/chain/ chain.bin Append-only record log state.cbor Chain state checkpoint ``` -Paths are defined in `src/soosef/paths.py`. +Paths are defined in `src/fieldwitness/paths.py`. -## 7. Migration from Verisoo-Only Attestations +## 7. Migration from Attest-Only Attestations -Existing Verisoo attestations in `~/.soosef/attestations/` are not modified. The chain -is a parallel structure. Migration is performed by the `soosef chain backfill` command: +Existing Attest attestations in `~/.fwmetadata/attestations/` are not modified. The chain +is a parallel structure. Migration is performed by the `fieldwitness chain backfill` command: -1. Iterate all records in Verisoo's `LocalStorage` (ordered by timestamp) +1. Iterate all records in Attest's `LocalStorage` (ordered by timestamp) 2. For each record, compute `content_hash = SHA-256(record.to_bytes())` 3. Create a chain record with: - - `content_type = "verisoo/attestation-v1"` - - `claimed_ts` set to the original Verisoo timestamp - - `metadata = {"backfilled": true, "original_ts": }` + - `content_type = "attest/attestation-v1"` + - `claimed_ts` set to the original Attest timestamp + - `metadata = {"backfilled": true, "original_ts": }` - Entropy witnesses collected at migration time (not original time) 4. Append to chain @@ -245,8 +245,8 @@ The `content_type` field identifies what was hashed into `content_hash`. Defined | Content Type | Description | |---|---| -| `verisoo/attestation-v1` | Verisoo `AttestationRecord` serialized bytes | -| `soosef/raw-file-v1` | Raw file bytes (for non-image attestations, future) | -| `soosef/metadata-only-v1` | No file content; metadata-only attestation (future) | +| `attest/attestation-v1` | Attest `AttestationRecord` serialized bytes | +| `fieldwitness/raw-file-v1` | Raw file bytes (for non-image attestations, future) | +| `fieldwitness/metadata-only-v1` | No file content; metadata-only attestation (future) | New content types may be added without changing the record format version. diff --git a/docs/architecture/export-bundle.md b/docs/architecture/export-bundle.md index 910bbb7..c2d2b4e 100644 --- a/docs/architecture/export-bundle.md +++ b/docs/architecture/export-bundle.md @@ -22,7 +22,7 @@ version, structured binary payload. ``` Offset Size Field ────── ───────── ────────────────────────────────────── -0 8 magic: b"SOOSEFX1" +0 8 magic: b"FIELDWITNESSX1" 8 1 version: uint8 (1) 9 4 summary_len: uint32 BE 13 var chain_summary: CBOR (see §3) @@ -39,7 +39,7 @@ All multi-byte integers are big-endian. The total bundle size is: ### Parsing Without Decryption To audit a bundle without decryption, read: -1. Magic (8 bytes) — verify `b"SOOSEFX1"` +1. Magic (8 bytes) — verify `b"FIELDWITNESSX1"` 2. Version (1 byte) — verify `1` 3. Summary length (4 bytes BE) — read the next N bytes as CBOR 4. Chain summary — verify signature, inspect metadata @@ -132,7 +132,7 @@ For each recipient, the DEK is wrapped using X25519 ECDH + HKDF + AES-256-GCM: 2. derived_key = HKDF-SHA256( ikm=shared_secret, salt=bundle_id, # binds to this specific bundle - info=b"soosef-dek-wrap-v1", + info=b"fieldwitness-dek-wrap-v1", length=32 ) 3. wrapped_dek = AES-256-GCM_Encrypt( @@ -185,7 +185,7 @@ A recipient decrypts a bundle: 2. Find own pubkey in recipients array 3. shared_secret = X25519_ECDH(recipient_x25519_private, sender_x25519_public) (sender_x25519_public derived from summary.signer_pubkey) -4. derived_key = HKDF-SHA256(shared_secret, salt=bundle_id, info=b"soosef-dek-wrap-v1") +4. derived_key = HKDF-SHA256(shared_secret, salt=bundle_id, info=b"fieldwitness-dek-wrap-v1") 5. dek = AES-256-GCM_Decrypt(derived_key, wrap_nonce, wrapped_dek, aad=bundle_id) 6. compressed = AES-256-GCM_Decrypt(dek, nonce, ciphertext, aad=summary_bytes) 7. records_cbor = zstd.decompress(compressed) @@ -240,11 +240,11 @@ These are two different trees: ## 6. Steganographic Embedding -Bundles can optionally be embedded in JPEG images using stegasoo's DCT steganography: +Bundles can optionally be embedded in JPEG images using stego's DCT steganography: ``` 1. bundle_bytes = create_export_bundle(chain, start, end, private_key, recipients) -2. stego_image = stegasoo.encode( +2. stego_image = stego.encode( carrier=carrier_image, reference=reference_image, file_data=bundle_bytes, @@ -256,14 +256,14 @@ Bundles can optionally be embedded in JPEG images using stegasoo's DCT steganogr Extraction: ``` -1. result = stegasoo.decode( +1. result = stego.decode( carrier=stego_image, reference=reference_image, passphrase=passphrase, channel_key=channel_key ) 2. bundle_bytes = result.file_data -3. assert bundle_bytes[:8] == b"SOOSEFX1" +3. assert bundle_bytes[:8] == b"FIELDWITNESSX1" ``` ### 6.1 Capacity Considerations @@ -299,7 +299,7 @@ recipient, the creator needs only their public key (no shared secret setup requi Recipients' Ed25519 public keys can be obtained via: - Direct exchange (QR code, USB transfer, verbal fingerprint verification) - Federation server identity registry (when available) -- Verisoo's existing `peers.json` file +- Attest's existing `peers.json` file ### 7.3 Self-Encryption @@ -310,7 +310,7 @@ This allows them to decrypt their own exports (e.g., when restoring from backup) | Error | Cause | Response | |---|---|---| -| Bad magic | Not a SOOSEFX1 bundle | Reject with `ExportError("not a SooSeF export bundle")` | +| Bad magic | Not a FIELDWITNESSX1 bundle | Reject with `ExportError("not a FieldWitness export bundle")` | | Bad version | Unsupported format version | Reject with `ExportError("unsupported bundle version")` | | Signature invalid | Tampered summary or wrong signer | Reject with `ExportError("bundle signature verification failed")` | | No matching recipient | Decryptor's key not in recipients list | Reject with `ExportError("not an authorized recipient")` | diff --git a/docs/architecture/federation-protocol.md b/docs/architecture/federation-protocol.md index a1c5de5..9462670 100644 --- a/docs/architecture/federation-protocol.md +++ b/docs/architecture/federation-protocol.md @@ -19,7 +19,7 @@ the underlying attestation data. | Term | Definition | |---|---| -| **Bundle** | An encrypted export bundle (SOOSEFX1 format) containing chain records | +| **Bundle** | An encrypted export bundle (FIELDWITNESSX1 format) containing chain records | | **STH** | Signed Tree Head — a server's signed commitment to its current Merkle tree state | | **Receipt** | A server-signed proof that a bundle was included in its log at a specific time | | **Inclusion proof** | Merkle path from a leaf (bundle hash) to the tree root | @@ -106,7 +106,7 @@ POST /v1/submit **Request body**: Raw bundle bytes (application/octet-stream) **Processing**: -1. Verify magic bytes `b"SOOSEFX1"` and version +1. Verify magic bytes `b"FIELDWITNESSX1"` and version 2. Parse chain summary 3. Verify `bundle_sig` against `signer_pubkey` 4. Compute `bundle_hash = SHA-256(0x00 || bundle_bytes)` @@ -215,7 +215,7 @@ GET /v1/entries?start={s}&end={e} 0: tree_index, # uint 1: bundle_hash, # bytes[32] 2: chain_summary, # CBOR map (from bundle, unencrypted) - 3: encrypted_blob, # bytes — full SOOSEFX1 bundle + 3: encrypted_blob, # bytes — full FIELDWITNESSX1 bundle 4: receipt_ts, # int — Unix µs when received } ``` @@ -488,8 +488,8 @@ CREATE INDEX idx_bundles_receipt_ts ON bundles(receipt_ts); "server_id": "my-server.example.org", "host": "0.0.0.0", "port": 8443, - "data_dir": "/var/lib/soosef-federation", - "identity_key_path": "/etc/soosef-federation/identity/private.pem", + "data_dir": "/var/lib/fieldwitness-federation", + "identity_key_path": "/etc/fieldwitness-federation/identity/private.pem", "peers": [ { "url": "https://peer1.example.org:8443", diff --git a/docs/architecture/federation.md b/docs/architecture/federation.md index 00c1ab1..9aec837 100644 --- a/docs/architecture/federation.md +++ b/docs/architecture/federation.md @@ -6,7 +6,7 @@ ## 1. Problem Statement -SooSeF operates offline-first: devices create Ed25519-signed attestations without network +FieldWitness operates offline-first: devices create Ed25519-signed attestations without network access. This creates two fundamental challenges: 1. **Timestamp credibility** — An offline device's clock is untrusted. An adversary with @@ -55,7 +55,7 @@ protecting content confidentiality even from the distribution infrastructure. ────────────── ─────── ──────── ┌──────────┐ ┌──────────────┐ ┌──────────┐ USB/SD ┌──────────┐ ┌────────────┐ - │ Verisoo │────>│ Hash Chain │────>│ Export │───────────────>│ Loader │────>│ Federation │ + │ Attest │────>│ Hash Chain │────>│ Export │───────────────>│ Loader │────>│ Federation │ │ Attest │ │ (Layer 1) │ │ Bundle │ │ (App) │ │ Server │ └──────────┘ └──────────────┘ │ (Layer 2)│ └────┬─────┘ └─────┬──────┘ └──────────┘ │ │ @@ -80,8 +80,8 @@ Each attestation is wrapped in a chain record that includes: - Entropy witnesses (system uptime, kernel state) that make timestamp fabrication expensive - An Ed25519 signature over the entire record -The chain lives on the offline device at `~/.soosef/chain/`. It wraps existing Verisoo -attestation records — the Verisoo record's bytes become the `content_hash` input. +The chain lives on the offline device at `~/.fwmetadata/chain/`. It wraps existing Attest +attestation records — the Attest record's bytes become the `content_hash` input. **See**: [chain-format.md](chain-format.md) @@ -94,7 +94,7 @@ A range of chain records is packaged into a portable bundle: 4. An unencrypted `chain_summary` (record count, hash range, Merkle root, signature) allows auditing without decryption -Bundles can optionally be embedded in JPEG images via stegasoo's DCT steganography, +Bundles can optionally be embedded in JPEG images via stego's DCT steganography, making them indistinguishable from normal photos on a USB stick. **See**: [export-bundle.md](export-bundle.md) @@ -123,13 +123,13 @@ The loader never needs signing keys — bundles are already signed. It is a tran ## 4. Key Domains -SooSeF maintains strict separation between two cryptographic domains: +FieldWitness maintains strict separation between two cryptographic domains: | Domain | Algorithm | Purpose | Key Location | |---|---|---|---| -| **Signing** | Ed25519 | Attestation signatures, chain records, bundle summaries | `~/.soosef/identity/` | +| **Signing** | Ed25519 | Attestation signatures, chain records, bundle summaries | `~/.fwmetadata/identity/` | | **Encryption** | X25519 + AES-256-GCM | Bundle payload encryption (envelope) | Derived from Ed25519 via birational map | -| **Steganography** | AES-256-GCM (from factors) | Stegasoo channel encryption | `~/.soosef/stegasoo/channel.key` | +| **Steganography** | AES-256-GCM (from factors) | Stego channel encryption | `~/.fwmetadata/stego/channel.key` | The signing and encryption domains share a key lineage (Ed25519 → X25519 derivation) but serve different purposes. The steganography domain remains fully independent — it protects @@ -215,7 +215,7 @@ more credible timestamps. Frequent USB sync trips shrink the window. ## 10. File Layout ``` -src/soosef/federation/ +src/fieldwitness/federation/ __init__.py models.py Chain record and state dataclasses serialization.py CBOR canonical encoding @@ -241,7 +241,7 @@ src/soosef/federation/ permissions.py Access control config.py Server configuration -~/.soosef/ +~/.fwmetadata/ chain/ Local hash chain chain.bin Append-only record log state.cbor Chain state checkpoint diff --git a/docs/deployment.md b/docs/deployment.md index cd34fa0..1c45e71 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,10 +1,10 @@ -# SooSeF Deployment Guide +# FieldWitness Deployment Guide Three-tier deployment model for field journalism, organizational evidence management, and cross-organization federation. This guide is for field deployers: IT staff at NGOs, technically competent journalists, -and anyone setting up SooSeF for operational use. Read the tier descriptions first, then +and anyone setting up FieldWitness for operational use. Read the tier descriptions first, then jump to the section that matches your deployment. --- @@ -28,7 +28,7 @@ LUKS-encrypted persistent partition for keys, config, and attestations. Amnesic pull the USB and the host machine retains nothing. **Tier 2 -- Org Server.** A persistent Docker deployment on a mini PC in the newsroom or a -trusted VPS. Runs the full SooSeF web UI (port 5000) and the federation API (port 8000). +trusted VPS. Runs the full FieldWitness web UI (port 5000) and the federation API (port 8000). Stores attestations, manages keys, and synchronizes with federation relays. **Tier 3 -- Federation Relay.** A lightweight Docker container on a VPS in a jurisdiction @@ -48,8 +48,8 @@ actionable. common, replaceable, good Linux support -- but any laptop that boots from USB works) - A build machine with `live-build` installed (any Debian/Ubuntu system) -The resulting USB image is a Debian Live system with SooSeF pre-installed. No pip, no -terminal, no manual setup. The reporter boots it and gets a working SooSeF instance. +The resulting USB image is a Debian Live system with FieldWitness pre-installed. No pip, no +terminal, no manual setup. The reporter boots it and gets a working FieldWitness instance. ### 1.2 Building the USB image @@ -85,29 +85,29 @@ When a reporter boots from the USB: 1. GRUB loads the Debian Live system 2. A minimal desktop environment starts (no login prompt) 3. Firefox opens automatically, pointed at `https://127.0.0.1:5000` -4. The SooSeF web UI prompts for first-user setup (on first boot) or login +4. The FieldWitness web UI prompts for first-user setup (on first boot) or login No terminal interaction required for normal operation. ### 1.4 Persistent encrypted storage The USB image includes a LUKS-encrypted persistent partition. On first boot, the reporter -sets a passphrase. All SooSeF state lives on this partition: +sets a passphrase. All FieldWitness state lives on this partition: ``` /persistent/ - .soosef/ Keys, config, attestations, chain data, auth + .fwmetadata/ Keys, config, attestations, chain data, auth ``` On subsequent boots, the system prompts for the LUKS passphrase to unlock the persistent -partition. If the passphrase is not entered (or wrong), SooSeF starts in a fresh, +partition. If the passphrase is not entered (or wrong), FieldWitness starts in a fresh, ephemeral state -- useful for crossing borders with a "clean" appearance. ### 1.5 Amnesic operation The live system runs from RAM. When the USB is removed: -- The host laptop retains zero SooSeF data (no files, no swap traces, no browser cache) +- The host laptop retains zero FieldWitness data (no files, no swap traces, no browser cache) - The host's own storage is never written to - RAM contents are gone on power-off @@ -162,17 +162,17 @@ cd deploy/docker docker compose up server -d ``` -This starts the full SooSeF server with: +This starts the full FieldWitness server with: - **Port 5000**: Web UI (Flask/Waitress) -- stego, attestation, key management, admin - **Port 8000**: Federation API (FastAPI/uvicorn) -- cross-org attestation sync The Docker image is a multi-stage build (`deploy/docker/Dockerfile`, target: `server`). -It installs SooSeF into a Python 3.12 virtualenv, copies frontend assets, and runs -`soosef init` on first start followed by `soosef serve`. +It installs FieldWitness into a Python 3.12 virtualenv, copies frontend assets, and runs +`fieldwitness init` on first start followed by `fieldwitness serve`. Data is persisted in a Docker volume (`server-data`) mounted at `/data` inside the -container. The `SOOSEF_DATA_DIR` environment variable points SooSeF at this volume. +container. The `FIELDWITNESS_DATA_DIR` environment variable points FieldWitness at this volume. ### 2.3 Docker Compose reference @@ -187,8 +187,8 @@ services: volumes: - server-data:/data environment: - - SOOSEF_DATA_DIR=/data - - VERISOO_GOSSIP_INTERVAL=60 + - FIELDWITNESS_DATA_DIR=/data + - FIELDWITNESS_GOSSIP_INTERVAL=60 relay: # Tier 3: Federation relay ports: @@ -196,7 +196,7 @@ services: volumes: - relay-data:/data environment: - - SOOSEF_DATA_DIR=/data + - FIELDWITNESS_DATA_DIR=/data ``` Adjust port mappings and volume drivers as needed for your environment. @@ -207,8 +207,8 @@ For organizations already running Kubernetes: ```bash # Build images -docker build -t soosef-server --target server -f deploy/docker/Dockerfile . -docker build -t soosef-relay --target relay -f deploy/docker/Dockerfile . +docker build -t fieldwitness-server --target server -f deploy/docker/Dockerfile . +docker build -t fieldwitness-relay --target relay -f deploy/docker/Dockerfile . # Deploy kubectl apply -f deploy/kubernetes/namespace.yaml @@ -218,7 +218,7 @@ kubectl apply -f deploy/kubernetes/server-deployment.yaml Important constraints from `deploy/kubernetes/README.md`: - **Single writer only.** Both deployments use `replicas: 1` with `Recreate` strategy. - SooSeF uses SQLite and append-only binary logs that require single-writer access. Do not + FieldWitness uses SQLite and append-only binary logs that require single-writer access. Do not scale horizontally. - **Persistent volumes required.** The server needs 10Gi, the relay needs 5Gi. Adjust based on expected attestation volume. @@ -234,21 +234,21 @@ be handled by: - A cloud load balancer (if on a VPS) For a simple mini-PC newsroom setup without a reverse proxy, override the CMD to remove -`--no-https` and let SooSeF generate a self-signed certificate. +`--no-https` and let FieldWitness generate a self-signed certificate. ### 2.6 Backups -The Docker volume contains all SooSeF state. Back it up: +The Docker volume contains all FieldWitness state. Back it up: ```bash # Stop the container, snapshot the volume, restart docker compose stop server docker run --rm -v server-data:/data -v /backup:/backup \ - busybox tar czf /backup/soosef-$(date +%Y%m%d).tar.gz -C /data . + busybox tar czf /backup/fieldwitness-$(date +%Y%m%d).tar.gz -C /data . docker compose start server ``` -Or use `soosef keys export` from inside the container for key-only backups. +Or use `fieldwitness keys export` from inside the container for key-only backups. --- @@ -279,7 +279,7 @@ This starts the relay with: - Data volume: `relay-data` at `/data` The Docker image uses the `relay` target from the same multi-stage Dockerfile. The relay -runs only `uvicorn soosef.verisoo.api:app` -- the minimal federation endpoint. +runs only `uvicorn fieldwitness.attest.api:app` -- the minimal federation endpoint. ### 3.3 Kubernetes deployment @@ -320,11 +320,11 @@ attestations. ## 4. Threat Level Configuration Presets -SooSeF ships four configuration presets at `deploy/config-presets/`. Choose one based on +FieldWitness ships four configuration presets at `deploy/config-presets/`. Choose one based on your operational threat environment and copy it to your config location. ```bash -cp deploy/config-presets/high-threat.json ~/.soosef/config.json +cp deploy/config-presets/high-threat.json ~/.fwmetadata/config.json ``` ### 4.1 Low Threat -- Press Freedom Country @@ -407,34 +407,34 @@ measures that are outside the scope of this software. ### 4.5 Customizing presets -The presets are starting points. Override individual settings in `~/.soosef/config.json` +The presets are starting points. Override individual settings in `~/.fwmetadata/config.json` after copying. The full configuration reference is in Section 8. --- ## 5. Initial Setup (All Tiers) -### 5.1 Initialize SooSeF +### 5.1 Initialize FieldWitness On Tier 1 (USB), initialization happens automatically on first boot. On Tier 2/3 (Docker), -the container runs `soosef init` on first start. For manual installs: +the container runs `fieldwitness init` on first start. For manual installs: ```bash -soosef init +fieldwitness init ``` -This creates the `~/.soosef/` directory structure: +This creates the `~/.fwmetadata/` directory structure: ``` -~/.soosef/ +~/.fwmetadata/ config.json Unified configuration - identity/ Ed25519 signing keypair (verisoo) + identity/ Ed25519 signing keypair (attest) private.pem public.pem identity.meta.json - stegasoo/ Stegasoo state + stego/ Stego state channel.key AES-256-GCM channel key - attestations/ Verisoo attestation log and index + attestations/ Attest attestation log and index chain/ Hash chain data anchors/ External timestamp anchors auth/ Web UI user database (SQLite) @@ -448,7 +448,7 @@ This creates the `~/.soosef/` directory structure: The `identity/` and `auth/` directories are created with mode 0700. -`soosef init` generates: +`fieldwitness init` generates: - An Ed25519 identity keypair (for signing attestations) - A channel key (for steganographic encoding) @@ -457,7 +457,7 @@ The `identity/` and `auth/` directories are created with mode 0700. ### 5.2 Apply a threat level preset ```bash -cp deploy/config-presets/-threat.json ~/.soosef/config.json +cp deploy/config-presets/-threat.json ~/.fwmetadata/config.json ``` See Section 4 for preset descriptions. @@ -467,7 +467,7 @@ See Section 4 for preset descriptions. Start the server and create the first admin user through the web UI: ```bash -soosef serve --host 0.0.0.0 --no-https +fieldwitness serve --host 0.0.0.0 --no-https ``` Navigate to `http://:5000` from a device on the same network. The web UI will @@ -510,12 +510,12 @@ Adjust the device path to match your partition layout. ### 6.2 Disable core dumps -A core dump from the SooSeF process would contain key material in plaintext. +A core dump from the FieldWitness process would contain key material in plaintext. ```bash echo "* hard core 0" | sudo tee -a /etc/security/limits.conf -echo "kernel.core_pattern=/dev/null" | sudo tee -a /etc/sysctl.d/99-soosef.conf -sudo sysctl -p /etc/sysctl.d/99-soosef.conf +echo "kernel.core_pattern=/dev/null" | sudo tee -a /etc/sysctl.d/99-fieldwitness.conf +sudo sysctl -p /etc/sysctl.d/99-fieldwitness.conf ``` ### 6.3 Firewall @@ -525,7 +525,7 @@ sudo apt install -y ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow ssh -sudo ufw allow 5000/tcp # SooSeF web UI +sudo ufw allow 5000/tcp # FieldWitness web UI sudo ufw allow 8000/tcp # Federation API (Tier 2 only) sudo ufw enable ``` @@ -553,39 +553,39 @@ Adjust based on what your system has running. The goal is to minimize attack sur ```bash # LAN-only, no HTTPS (acceptable if the network is physically isolated) -soosef serve --host 0.0.0.0 --no-https +fieldwitness serve --host 0.0.0.0 --no-https # With self-signed HTTPS (recommended) -soosef serve --host 0.0.0.0 +fieldwitness serve --host 0.0.0.0 # Custom port -soosef serve --host 0.0.0.0 --port 8443 +fieldwitness serve --host 0.0.0.0 --port 8443 ``` -On first HTTPS start, SooSeF auto-generates a self-signed certificate at -`~/.soosef/certs/cert.pem`. Browsers will show a certificate warning -- this is expected +On first HTTPS start, FieldWitness auto-generates a self-signed certificate at +`~/.fwmetadata/certs/cert.pem`. Browsers will show a certificate warning -- this is expected for self-signed certs. Instruct users to accept the warning or distribute the cert file to client devices. -SooSeF uses Waitress (pure Python, no C dependencies) as its production server with 4 +FieldWitness uses Waitress (pure Python, no C dependencies) as its production server with 4 worker threads by default. Adjust with `--workers`. ### 7.2 systemd service (bare metal Tier 2) -Create `/etc/systemd/system/soosef.service`: +Create `/etc/systemd/system/fieldwitness.service`: ```ini [Unit] -Description=SooSeF Security Fieldkit +Description=FieldWitness Security Fieldkit After=network.target [Service] Type=simple -User=soosef -Group=soosef -WorkingDirectory=/home/soosef -Environment="PATH=/home/soosef/soosef-env/bin:/usr/bin" -ExecStart=/home/soosef/soosef-env/bin/soosef serve --host 0.0.0.0 --workers 4 +User=fieldwitness +Group=fieldwitness +WorkingDirectory=/home/fieldwitness +Environment="PATH=/home/fieldwitness/fieldwitness-env/bin:/usr/bin" +ExecStart=/home/fieldwitness/fieldwitness-env/bin/fieldwitness serve --host 0.0.0.0 --workers 4 Restart=on-failure RestartSec=5 @@ -593,7 +593,7 @@ RestartSec=5 NoNewPrivileges=yes ProtectSystem=strict ProtectHome=read-only -ReadWritePaths=/home/soosef/.soosef +ReadWritePaths=/home/fieldwitness/.fieldwitness PrivateTmp=yes [Install] @@ -604,8 +604,8 @@ Enable and start: ```bash sudo systemctl daemon-reload -sudo systemctl enable --now soosef -sudo journalctl -u soosef -f # Watch logs +sudo systemctl enable --now fieldwitness +sudo journalctl -u fieldwitness -f # Watch logs ``` Add `--no-https` to `ExecStart` if running on a physically isolated LAN where TLS is @@ -615,7 +615,7 @@ unnecessary. ## 8. Configuration Reference -Configuration lives at `~/.soosef/config.json`. Edit it directly or use the web admin +Configuration lives at `~/.fwmetadata/config.json`. Edit it directly or use the web admin panel. All fields have sensible defaults -- you only need to set what you want to change. | Field | Default | Description | @@ -628,7 +628,7 @@ panel. All fields have sensible defaults -- you only need to set what you want t | `session_timeout_minutes` | `15` | Idle session expiry. Lower is safer. | | `login_lockout_attempts` | `5` | Failed logins before lockout. | | `login_lockout_minutes` | `15` | Lockout duration after exceeding failed login attempts. | -| `default_embed_mode` | `auto` | Default steganographic embedding mode for Stegasoo. | +| `default_embed_mode` | `auto` | Default steganographic embedding mode for Stego. | | `killswitch_enabled` | `false` | Enable software killswitch. | | `deadman_enabled` | `false` | Enable dead man's switch. | | `deadman_interval_hours` | `24` | Hours between required check-ins. | @@ -637,8 +637,8 @@ panel. All fields have sensible defaults -- you only need to set what you want t | `usb_monitoring_enabled` | `false` | Monitor for unauthorized USB devices. | | `tamper_monitoring_enabled` | `false` | File integrity monitoring. | | `chain_enabled` | `true` | Wrap attestations in a hash chain. | -| `chain_auto_wrap` | `true` | Automatically chain verisoo attestations. | -| `backup_reminder_days` | `7` | Warn if no backup in this many days. `soosef status` reports overdue backups. | +| `chain_auto_wrap` | `true` | Automatically chain attest attestations. | +| `backup_reminder_days` | `7` | Warn if no backup in this many days. `fieldwitness status` reports overdue backups. | | `cover_name` | `""` | If set, used as the CN in the self-signed SSL certificate instead of "localhost". See Section 15 (Cover/Duress Mode). | Example minimal config for a high-threat field deployment: @@ -669,14 +669,14 @@ deployment type. On Tier 1 (USB), the killswitch destroys the LUKS key. On Tier ### 9.1 Dead man's switch -The dead man's switch requires periodic check-ins. If you miss a check-in, SooSeF sends +The dead man's switch requires periodic check-ins. If you miss a check-in, FieldWitness sends a warning during the grace period. If the grace period expires without a check-in, the killswitch fires automatically and destroys all key material and data. Arm it: ```bash -soosef fieldkit deadman arm --interval 12 --grace 1 +fieldwitness fieldkit deadman arm --interval 12 --grace 1 ``` This requires a check-in every 12 hours, with a 1-hour grace period. @@ -684,7 +684,7 @@ This requires a check-in every 12 hours, with a 1-hour grace period. Check in: ```bash -soosef fieldkit checkin +fieldwitness fieldkit checkin ``` You can also check in through the web UI at `/fieldkit`. @@ -692,10 +692,10 @@ You can also check in through the web UI at `/fieldkit`. Check status: ```bash -soosef status +fieldwitness status ``` -The dead man's switch enforcement loop runs as a background thread inside `soosef serve`, +The dead man's switch enforcement loop runs as a background thread inside `fieldwitness serve`, checking every 60 seconds. It will send a webhook warning (if configured via `deadman_warning_webhook`) during the grace period, then execute a full purge if the grace period expires. The webhook must be a public URL -- SSRF protection blocks private/internal @@ -704,7 +704,7 @@ IP ranges. For cron-based enforcement outside the web server (e.g., on a headless node), use: ```bash -soosef fieldkit check-deadman +fieldwitness fieldkit check-deadman ``` Exit codes: 0 = not armed or not overdue, 1 = unexpected error, 2 = killswitch fired. @@ -712,16 +712,16 @@ Exit codes: 0 = not armed or not overdue, 1 = unexpected error, 2 = killswitch f Disarm: ```bash -soosef fieldkit deadman disarm +fieldwitness fieldkit deadman disarm ``` ### 9.2 Geofence -If you have a USB GPS module, you can set a geographic boundary. SooSeF will trigger the +If you have a USB GPS module, you can set a geographic boundary. FieldWitness will trigger the killswitch if the device moves outside the fence. ```bash -soosef fieldkit geofence set --lat 50.4501 --lon 30.5234 --radius 5000 +fieldwitness fieldkit geofence set --lat 50.4501 --lon 30.5234 --radius 5000 ``` Coordinates are in decimal degrees, radius in meters. Most useful on Tier 1 field devices. @@ -731,10 +731,10 @@ Coordinates are in decimal degrees, radius in meters. Most useful on Tier 1 fiel Record currently connected USB devices as the trusted baseline: ```bash -soosef fieldkit usb snapshot +fieldwitness fieldkit usb snapshot ``` -When monitoring is enabled, SooSeF will alert (or trigger killswitch, depending on config) +When monitoring is enabled, FieldWitness will alert (or trigger killswitch, depending on config) if an unknown USB device is connected. ### 9.4 Tamper baseline @@ -742,10 +742,10 @@ if an unknown USB device is connected. Record file integrity baselines for critical files: ```bash -soosef fieldkit tamper baseline +fieldwitness fieldkit tamper baseline ``` -SooSeF monitors for unexpected changes to tracked files when tamper monitoring is enabled. +FieldWitness monitors for unexpected changes to tracked files when tamper monitoring is enabled. ### 9.5 Killswitch @@ -760,9 +760,9 @@ maximize what is gone before any interruption: 5. **Attestation log and chain data** 6. **Temp files and audit log** 7. **Configuration** -8. **System journal entries** for the soosef unit +8. **System journal entries** for the fieldwitness unit 9. **Deep forensic scrub** (see below) -10. **Self-uninstall** of the soosef pip package +10. **Self-uninstall** of the fieldwitness pip package On Tier 1 (USB), steps 1-10 are replaced by LUKS header destruction, which is faster and more reliable on flash media (see Section 1.6). @@ -770,25 +770,25 @@ more reliable on flash media (see Section 1.6). Trigger manually: ```bash -soosef fieldkit purge --confirm CONFIRM-PURGE +fieldwitness fieldkit purge --confirm CONFIRM-PURGE ``` **Deep forensic scrub (Tier 2 bare metal only).** When the killswitch fires with `ALL` scope on a non-USB deployment, it performs a deep forensic scrub that removes traces of -SooSeF beyond the `~/.soosef/` directory: +FieldWitness beyond the `~/.fwmetadata/` directory: - **Python bytecache**: removes all `__pycache__` directories and `.pyc` files for - soosef, stegasoo, and verisoo from site-packages + fieldwitness, stego, and attest from site-packages - **pip dist-info**: removes package metadata directories that would reveal what was installed - **pip download cache**: removes cached wheels and source distributions under - `~/.cache/pip/` matching soosef/stegasoo/verisoo + `~/.cache/pip/` matching fieldwitness/stego/attest - **Shell history**: rewrites `~/.bash_history`, `~/.zsh_history`, and fish history to - remove all lines containing "soosef" -- **Self-uninstall**: runs `pip uninstall -y soosef` to remove the package from the + remove all lines containing "fieldwitness" +- **Self-uninstall**: runs `pip uninstall -y fieldwitness` to remove the package from the virtual environment -After a full purge, the system will show minimal evidence that SooSeF was ever installed. +After a full purge, the system will show minimal evidence that FieldWitness was ever installed. Note that this is best-effort -- filesystem journal entries, inode metadata, and flash wear-leveling remnants may still exist. For complete deniability on Tier 2, use full-disk encryption (LUKS) and physically destroy the storage media. On Tier 1, LUKS header @@ -798,11 +798,11 @@ destruction handles this. ## 10. Key Management -SooSeF manages two separate key domains: +FieldWitness manages two separate key domains: -- **Ed25519 identity key** (`~/.soosef/identity/`) -- used for signing attestations. +- **Ed25519 identity key** (`~/.fwmetadata/identity/`) -- used for signing attestations. This is your provenance identity. -- **AES-256-GCM channel key** (`~/.soosef/stegasoo/channel.key`) -- used for +- **AES-256-GCM channel key** (`~/.fwmetadata/stego/channel.key`) -- used for steganographic encoding/decoding. Shared with anyone who needs to read your stego messages. @@ -810,11 +810,11 @@ These are separate security concerns and are never merged. ### 10.1 Backup -Back up keys regularly. SooSeF warns if no backup has been taken within the +Back up keys regularly. FieldWitness warns if no backup has been taken within the `backup_reminder_days` window (default: 7 days). ```bash -soosef keys export /media/usb/soosef-backup.enc +fieldwitness keys export /media/usb/fieldwitness-backup.enc ``` This creates an encrypted bundle. You will be prompted for a passphrase. Store the USB @@ -826,7 +826,7 @@ keys are gone. Back up to a second USB drive and store it in a separate location ### 10.2 Restore ```bash -soosef keys import /media/usb/soosef-backup.enc +fieldwitness keys import /media/usb/fieldwitness-backup.enc ``` ### 10.3 Key rotation @@ -834,13 +834,13 @@ soosef keys import /media/usb/soosef-backup.enc Rotate the identity keypair (old key is archived, not destroyed): ```bash -soosef keys rotate-identity +fieldwitness keys rotate-identity ``` Rotate the channel key: ```bash -soosef keys rotate-channel +fieldwitness keys rotate-channel ``` After rotating keys, take a fresh backup immediately. Notify all collaborators of the @@ -851,7 +851,7 @@ new identity fingerprint so they can update their trusted-key lists. Import a collaborator's public key so you can verify their attestations: ```bash -soosef keys trust --import /media/usb/collaborator-pubkey.pem +fieldwitness keys trust --import /media/usb/collaborator-pubkey.pem ``` Verify the fingerprint out-of-band (in person, over a secure channel) before trusting. @@ -861,7 +861,7 @@ Verify the fingerprint out-of-band (in person, over a secure channel) before tru ## 11. Source Drop Box The source drop box provides a SecureDrop-like anonymous file intake that runs inside -SooSeF. Sources do not need a SooSeF account -- they receive a one-time upload URL and +FieldWitness. Sources do not need a FieldWitness account -- they receive a one-time upload URL and submit files through their browser. ### 11.1 Creating tokens @@ -908,7 +908,7 @@ the receipt is valid. ### 11.5 Operational security for the drop box -- **No SooSeF branding**: the upload page is a minimal HTML form with no identifying +- **No FieldWitness branding**: the upload page is a minimal HTML form with no identifying marks, styled generically - **No authentication required**: the source never creates an account or reveals identity information @@ -918,21 +918,21 @@ the receipt is valid. - **Tor compatibility**: the upload page is a self-contained HTML page with inline JavaScript (SubtleCrypto only) and no external resources. It works over Tor Browser with JavaScript enabled. No CDN, no fonts, no analytics -- **No IP logging**: SooSeF does not log source IP addresses. Ensure your reverse proxy +- **No IP logging**: FieldWitness does not log source IP addresses. Ensure your reverse proxy (if any) also does not log access. If running behind Tor, the source's real IP is never visible to the server - **Receipt codes are deterministic**: the receipt is an HMAC of the file's SHA-256 keyed by the token, so the source can independently verify it corresponds to their file -If operating in a high-risk environment, consider running SooSeF as a Tor hidden service +If operating in a high-risk environment, consider running FieldWitness as a Tor hidden service (`.onion` address). Configure a torrc hidden service pointing to `127.0.0.1:5000` and share the `.onion` URL instead of a LAN address. ### 11.6 Drop box file storage -Uploaded files are stored in `~/.soosef/temp/dropbox/` with filenames derived from the +Uploaded files are stored in `~/.fwmetadata/temp/dropbox/` with filenames derived from the SHA-256 prefix. This directory has mode 0700. Token metadata and receipts are stored in a -SQLite database at `~/.soosef/auth/dropbox.db`. +SQLite database at `~/.fwmetadata/auth/dropbox.db`. --- @@ -957,11 +957,11 @@ If the device has internet access (even temporarily), submit the chain head to a Timestamping Authority: ```bash -soosef chain anchor --tsa https://freetsa.org/tsr +fieldwitness chain anchor --tsa https://freetsa.org/tsr ``` This sends the chain head digest to the TSA, receives a signed timestamp token, and saves -both the anchor and the TSA response as a JSON file under `~/.soosef/chain/anchors/`. +both the anchor and the TSA response as a JSON file under `~/.fwmetadata/chain/anchors/`. The TSA token is a cryptographically signed proof from a third party that the hash existed at the timestamp. This is legally stronger than a self-asserted timestamp. @@ -971,7 +971,7 @@ at the timestamp. This is legally stronger than a self-asserted timestamp. Without `--tsa`, the command exports the anchor hash for manual external submission: ```bash -soosef chain anchor +fieldwitness chain anchor ``` This prints a compact text block containing the chain ID, head index, record count, and @@ -989,7 +989,7 @@ The anchor file is saved locally regardless of whether a TSA was used. For Tier 1 (USB) and other airgapped deployments: -1. Run `soosef chain anchor` on the airgapped device +1. Run `fieldwitness chain anchor` on the airgapped device 2. Copy the printed anchor text to a USB drive (text file, photograph of screen, or paper transcription) 3. On an internet-connected device, publish the anchor text to one or more external @@ -1002,7 +1002,7 @@ For Tier 1 (USB) and other airgapped deployments: To verify that the current chain state matches a previously created anchor: ```bash -soosef chain verify +fieldwitness chain verify ``` This checks all hash linkage and signatures in the chain. If the chain has been tampered @@ -1012,7 +1012,7 @@ with since the anchor was created, verification will fail. ## 13. Cross-Organization Federation -Federation allows multiple SooSeF instances to exchange attestation records for +Federation allows multiple FieldWitness instances to exchange attestation records for collaborative investigations. Bundles are self-authenticating: each record carries the signer's public key, so the importer can verify signatures against their trust store. @@ -1031,14 +1031,14 @@ On Organization A: ```bash # Export public key -cp ~/.soosef/identity/public.pem /media/usb/org-a-pubkey.pem +cp ~/.fwmetadata/identity/public.pem /media/usb/org-a-pubkey.pem ``` On Organization B: ```bash # Import Org A's key and verify fingerprint -soosef keys trust --import /media/usb/org-a-pubkey.pem +fieldwitness keys trust --import /media/usb/org-a-pubkey.pem ``` Always verify fingerprints out-of-band (in person, over a known-secure voice channel). @@ -1049,13 +1049,13 @@ Repeat in both directions so each organization trusts the other. Export a JSON bundle containing attestation records and chain data: ```bash -soosef chain export --output /media/usb/investigation-bundle.zip +fieldwitness chain export --output /media/usb/investigation-bundle.zip ``` To export only records from a specific index range: ```bash -soosef chain export --start 100 --end 200 --output /media/usb/partial-bundle.zip +fieldwitness chain export --start 100 --end 200 --output /media/usb/partial-bundle.zip ``` The export includes: @@ -1068,7 +1068,7 @@ The export includes: ### 13.3 Importing attestation bundles -On the receiving organization's SooSeF instance: +On the receiving organization's FieldWitness instance: - Records are imported into the local attestation log with a `federated_from` metadata tag - Records signed by untrusted fingerprints are rejected (unless trust-on-first-use is used) @@ -1077,7 +1077,7 @@ On the receiving organization's SooSeF instance: ### 13.4 Delivery acknowledgments When a bundle is imported and the receiving instance has a chain store and private key, -SooSeF automatically creates a delivery acknowledgment record in the local chain. This +FieldWitness automatically creates a delivery acknowledgment record in the local chain. This records the bundle hash, sender fingerprint, and count of records received. The acknowledgment provides a cryptographic receipt that the bundle was delivered and ingested. @@ -1087,7 +1087,7 @@ To produce evidence for a court order or legal discovery request without reveali entire chain: ```bash -soosef chain disclose --indices 42,43,44 --output disclosure.json +fieldwitness chain disclose --indices 42,43,44 --output disclosure.json ``` This exports a proof bundle where the selected records are shown in full and all other @@ -1110,7 +1110,7 @@ Tier 1 field devices. ## 14. Evidence Packages and Cold Archives -SooSeF provides two export formats for preserving evidence outside the running instance. +FieldWitness provides two export formats for preserving evidence outside the running instance. ### 14.1 Evidence packages @@ -1127,21 +1127,21 @@ Contents of an evidence package: `cryptography`) - `README.txt` -- human-readable instructions -The package is self-contained. No SooSeF installation is required to verify the evidence. +The package is self-contained. No FieldWitness installation is required to verify the evidence. The standalone `verify.py` script checks image SHA-256 hashes against attestation records and verifies chain hash linkage. **When to create evidence packages:** - Before handing evidence to a legal team -- When sharing with a partner organization that does not run SooSeF +- When sharing with a partner organization that does not run FieldWitness - For court submission (the self-contained verifier is the key feature) - Before any action that might destroy the running instance (travel through hostile checkpoints, anticipated raids) ### 14.2 Cold archives -A cold archive is a full snapshot of the entire SooSeF evidence store, designed for +A cold archive is a full snapshot of the entire FieldWitness evidence store, designed for long-term preservation. It follows OAIS (ISO 14721) alignment: the archive is self-describing, includes its own verification code, and documents the cryptographic algorithms used. @@ -1149,7 +1149,7 @@ algorithms used. Contents of a cold archive: - `chain/` -- raw append-only hash chain binary, state checkpoint, and anchor files -- `attestations/` -- full verisoo attestation log and LMDB index +- `attestations/` -- full attest attestation log and LMDB index - `keys/public.pem` -- signer's public key - `keys/bundle.enc` -- encrypted key bundle (optional, password-protected) - `keys/trusted/` -- trusted collaborator public keys @@ -1157,13 +1157,13 @@ Contents of a cold archive: - `verify.py` -- standalone verification script - `ALGORITHMS.txt` -- documents all cryptographic algorithms and formats used (Ed25519, SHA-256, AES-256-GCM, Argon2id, CBOR, etc.) so the archive remains verifiable even if - SooSeF no longer exists + FieldWitness no longer exists - `README.txt` -- human-readable description -To restore a cold archive on a fresh SooSeF instance: +To restore a cold archive on a fresh FieldWitness instance: ```bash -soosef archive import +fieldwitness archive import ``` **When to create cold archives:** @@ -1178,13 +1178,13 @@ soosef archive import For legal discovery and court proceedings: -1. Use `soosef chain disclose` for selective disclosure (Section 13.5) when you must +1. Use `fieldwitness chain disclose` for selective disclosure (Section 13.5) when you must respond to a specific request without revealing the full chain 2. Use evidence packages for handing specific images and their attestations to counsel 3. Use cold archives when full preservation is required All three formats include standalone verification scripts so that the receiving party does -not need to install SooSeF. +not need to install FieldWitness. ### 14.4 Long-term archival best practices @@ -1202,24 +1202,24 @@ not need to install SooSeF. ## 15. Cover/Duress Mode -Cover mode disguises a SooSeF installation so that casual inspection of the device does +Cover mode disguises a FieldWitness installation so that casual inspection of the device does not immediately reveal it as a security toolkit. ### 15.1 Renaming the data directory -By default, SooSeF stores everything under `~/.soosef/`. To use an inconspicuous name, -set the `SOOSEF_DATA_DIR` environment variable: +By default, FieldWitness stores everything under `~/.fwmetadata/`. To use an inconspicuous name, +set the `FIELDWITNESS_DATA_DIR` environment variable: ```bash -export SOOSEF_DATA_DIR=~/.local/share/inventory -soosef init +export FIELDWITNESS_DATA_DIR=~/.local/share/inventory +fieldwitness init ``` -All SooSeF commands respect this variable. Add it to the soosef user's shell profile or +All FieldWitness commands respect this variable. Add it to the fieldwitness user's shell profile or the systemd service file: ```ini -Environment="SOOSEF_DATA_DIR=/home/soosef/.local/share/inventory" +Environment="FIELDWITNESS_DATA_DIR=/home/fieldwitness/.local/share/inventory" ``` In Docker deployments, set this in the environment section of the compose file. The @@ -1237,7 +1237,7 @@ a browser inspector sees a plausible-looking certificate: } ``` -Delete `~/.soosef/certs/cert.pem` and restart the server to regenerate the certificate +Delete `~/.fwmetadata/certs/cert.pem` and restart the server to regenerate the certificate with the new CN. The threat level presets (Section 4) include appropriate cover names for each level. @@ -1245,7 +1245,7 @@ The threat level presets (Section 4) include appropriate cover names for each le ### 15.3 Portable USB operation (Tier 1) The Tier 1 bootable USB is the primary cover mechanism. When the USB is not inserted, the -host laptop shows no trace of SooSeF. The USB itself is a LUKS-encrypted partition that +host laptop shows no trace of FieldWitness. The USB itself is a LUKS-encrypted partition that reveals nothing without the passphrase. For additional cover, the USB can be labeled generically (e.g., "DEBIAN LIVE") and the @@ -1255,19 +1255,19 @@ LUKS partition does not advertise its contents. ## 16. Operational Security Notes -SooSeF is a tool, not a shield. Understand what it cannot do. +FieldWitness is a tool, not a shield. Understand what it cannot do. -### What SooSeF does not protect against +### What FieldWitness does not protect against - **Physical coercion.** If someone forces you to unlock the device or reveal passwords, no software can help. The killswitch is for situations where you can act before interception, not during. -- **Social engineering.** SooSeF cannot prevent users from being tricked into revealing +- **Social engineering.** FieldWitness cannot prevent users from being tricked into revealing credentials or disabling security features. - **Leaving the browser open.** The session timeout helps, but if someone walks up to an unlocked browser session, they have access. Train users to close the browser or lock the screen. -- **Compromised client devices.** SooSeF secures the server. If a user's laptop has +- **Compromised client devices.** FieldWitness secures the server. If a user's laptop has malware, their browser session is compromised regardless of what the server does. - **Tier 3 relay compromise.** If the relay is seized, attestation metadata (hashes, signatures, timestamps) is exposed. This reveals that certain public keys attested @@ -1285,7 +1285,7 @@ reliable** because: - Wear leveling distributes writes across the flash, meaning the original block may be preserved. -SooSeF's defense against this is **cryptographic erasure**: destroy the keys first, then +FieldWitness's defense against this is **cryptographic erasure**: destroy the keys first, then the data. Even if fragments of encrypted data survive on flash, they are useless without the keys. The killswitch destroys keys before anything else, and keys are small enough to fit in a single flash block. @@ -1318,7 +1318,7 @@ This is a fundamental limitation of Python-based security tools. ### Health check -SooSeF exposes a `/health` endpoint on the web UI. Hit it to verify the server is running: +FieldWitness exposes a `/health` endpoint on the web UI. Hit it to verify the server is running: ```bash # Tier 1 or bare metal @@ -1336,7 +1336,7 @@ The `-k` flag skips certificate verification for self-signed certs. ### System status ```bash -soosef status +fieldwitness status ``` This checks identity key, channel key, trusted keys, dead man's switch state, geofence, @@ -1348,7 +1348,7 @@ chain status, and backup status. Use `--json` for machine-readable output. 1. Check that `host` is set to `0.0.0.0` in config, not `127.0.0.1` 2. Check firewall: `sudo ufw status` -- port 5000 must be allowed -3. Check the service is running: `sudo systemctl status soosef` (bare metal) or +3. Check the service is running: `sudo systemctl status fieldwitness` (bare metal) or `docker compose ps` (Docker) 4. Check the machine's IP: `ip addr show` @@ -1361,13 +1361,13 @@ docker compose logs server ``` Common causes: port conflict (5000 or 8000 already in use), volume permission issues, -or missing initialization. The container runs `soosef init` on first start, which requires +or missing initialization. The container runs `fieldwitness init` on first start, which requires write access to the `/data` volume. **Certificate warnings in browser** Expected with self-signed certificates. Users must click through the warning. To avoid -this, distribute `~/.soosef/certs/cert.pem` to client devices and install it as a +this, distribute `~/.fwmetadata/certs/cert.pem` to client devices and install it as a trusted certificate. **Dead man's switch fires unexpectedly** @@ -1380,16 +1380,16 @@ start. Make sure the systemd service is set to `Restart=on-failure` (bare metal) If you need to perform maintenance, disarm the switch first: ```bash -soosef fieldkit deadman disarm +fieldwitness fieldkit deadman disarm ``` Re-arm when maintenance is complete. -**Permission errors on ~/.soosef/** +**Permission errors on ~/.fwmetadata/** The `identity/`, `auth/`, and `certs/` directories are mode 0700. If running under a -different user than the one who ran `soosef init`, you will get permission denied errors. -Always run SooSeF as the same user. In Docker, the container runs as the `soosef` user +different user than the one who ran `fieldwitness init`, you will get permission denied errors. +Always run FieldWitness as the same user. In Docker, the container runs as the `fieldwitness` user created during image build. **Drop box tokens expire immediately** @@ -1410,7 +1410,7 @@ sudo date -s "2026-04-01 12:00:00" **Chain anchor TSA submission fails** TSA submission requires network access. On Tier 1 (USB) or other airgapped devices, use -manual anchoring instead (`soosef chain anchor` without `--tsa`). If the TSA URL is +manual anchoring instead (`fieldwitness chain anchor` without `--tsa`). If the TSA URL is unreachable, the anchor is still saved locally -- only the external timestamp token is missing. @@ -1420,8 +1420,8 @@ If you set `cover_name` after the certificate was already generated, delete the certificate and restart: ```bash -rm ~/.soosef/certs/cert.pem ~/.soosef/certs/key.pem -sudo systemctl restart soosef +rm ~/.fwmetadata/certs/cert.pem ~/.fwmetadata/certs/key.pem +sudo systemctl restart fieldwitness ``` **Account lockout after repeated failed logins** @@ -1452,15 +1452,15 @@ python verify.py **Kubernetes pod stuck in CrashLoopBackOff** -Check logs with `kubectl logs -n soosef `. Common cause: the PersistentVolumeClaim -is not bound. Verify with `kubectl get pvc -n soosef`. The server needs 10Gi and the relay +Check logs with `kubectl logs -n fieldwitness `. Common cause: the PersistentVolumeClaim +is not bound. Verify with `kubectl get pvc -n fieldwitness`. The server needs 10Gi and the relay needs 5Gi. --- ## Appendix A: Legacy Raspberry Pi Deployment -The Raspberry Pi was the original deployment target for SooSeF. It remains a viable +The Raspberry Pi was the original deployment target for FieldWitness. It remains a viable option for fixed installations (e.g., a permanently installed newsroom server that does not need to be portable). The three-tier model supersedes the RPi as the primary deployment for most use cases because: @@ -1477,7 +1477,7 @@ If you still want to use a Raspberry Pi for a fixed Tier 2 server: libjpeg62-turbo-dev zlib1g-dev libffi-dev libssl-dev gfortran libopenblas-dev` - Follow the Tier 2 bare metal instructions (systemd service, hardening) in this guide - For GPIO hardware killswitch support, install the `rpi` extra: - `pip install "soosef[rpi]"` (includes `gpiozero`) + `pip install "fieldwitness[rpi]"` (includes `gpiozero`) - Wire a momentary push button between GPIO 17 and 3.3V with a 10k pull-down to GND. Default: 5-second hold to trigger. Configurable via `gpio_killswitch_hold_seconds` diff --git a/docs/evidence-guide.md b/docs/evidence-guide.md index 5f8e9ec..032837e 100644 --- a/docs/evidence-guide.md +++ b/docs/evidence-guide.md @@ -1,20 +1,20 @@ # Evidence Guide **Audience**: Journalists, investigators, and legal teams who need to create, export, and -verify evidence packages from SooSeF. +verify evidence packages from FieldWitness. -**Prerequisites**: A running SooSeF instance with at least one attested image or file. +**Prerequisites**: A running FieldWitness instance with at least one attested image or file. Familiarity with basic CLI commands. --- ## Overview -SooSeF provides three mechanisms for preserving and sharing evidence outside a running +FieldWitness provides three mechanisms for preserving and sharing evidence outside a running instance: evidence packages (for handing specific files to third parties), cold archives (full-state preservation for 10+ year horizons), and selective disclosure (proving specific records without revealing the rest of the chain). All three include standalone verification -scripts that require no SooSeF installation. +scripts that require no FieldWitness installation. --- @@ -37,24 +37,24 @@ verification of specific attested images or files. ```bash # Package specific images with their attestation records -$ soosef evidence export photo1.jpg photo2.jpg --output evidence_package.zip +$ fieldwitness evidence export photo1.jpg photo2.jpg --output evidence_package.zip # Filter by investigation tag -$ soosef evidence export photo1.jpg --investigation "case-2026-001" \ +$ fieldwitness evidence export photo1.jpg --investigation "case-2026-001" \ --output evidence_case001.zip ``` ### When to create evidence packages - Before handing evidence to a legal team or court -- When sharing with a partner organization that does not run SooSeF +- When sharing with a partner organization that does not run FieldWitness - Before crossing a hostile checkpoint (create the package, send it to a trusted party, then activate the killswitch if needed) - When an investigation is complete and files must be archived independently ### Verifying an evidence package -The recipient does not need SooSeF. They need only Python 3.11+ and the `cryptography` +The recipient does not need FieldWitness. They need only Python 3.11+ and the `cryptography` pip package: ```bash @@ -75,9 +75,9 @@ The verification script checks: ## Cold Archives -A cold archive is a full snapshot of the entire SooSeF evidence store, designed for +A cold archive is a full snapshot of the entire FieldWitness evidence store, designed for long-term preservation aligned with OAIS (ISO 14721). It is self-describing and includes -everything needed to verify the evidence decades later, even if SooSeF no longer exists. +everything needed to verify the evidence decades later, even if FieldWitness no longer exists. ### What is inside a cold archive @@ -86,7 +86,7 @@ everything needed to verify the evidence decades later, even if SooSeF no longer | `chain/chain.bin` | Raw append-only hash chain binary | | `chain/state.cbor` | Chain state checkpoint | | `chain/anchors/` | External timestamp anchor files (RFC 3161 tokens, manual anchors) | -| `attestations/log.bin` | Full verisoo attestation log | +| `attestations/log.bin` | Full attest attestation log | | `attestations/index/` | LMDB index files | | `keys/public.pem` | Signer's Ed25519 public key | | `keys/bundle.enc` | Encrypted key bundle (optional, password-protected) | @@ -100,10 +100,10 @@ everything needed to verify the evidence decades later, even if SooSeF no longer ```bash # Full archive without encrypted key bundle -$ soosef archive export --output archive_20260401.zip +$ fieldwitness archive export --output archive_20260401.zip # Include encrypted key bundle (will prompt for passphrase) -$ soosef archive export --include-keys --output archive_20260401.zip +$ fieldwitness archive export --include-keys --output archive_20260401.zip ``` > **Warning:** If you include the encrypted key bundle, store the passphrase separately @@ -119,11 +119,11 @@ $ soosef archive export --include-keys --output archive_20260401.zip ### Restoring from a cold archive -On a fresh SooSeF instance: +On a fresh FieldWitness instance: ```bash -$ soosef init -$ soosef archive import archive_20260401.zip +$ fieldwitness init +$ fieldwitness archive import archive_20260401.zip ``` ### Long-term archival best practices @@ -133,7 +133,7 @@ $ soosef archive import archive_20260401.zip 3. Include the encrypted key bundle in the archive with a strong passphrase 4. Periodically verify archive integrity: unzip and run `python verify.py` 5. The `ALGORITHMS.txt` file documents every algorithm and parameter used, so a verifier - can be written from scratch even if SooSeF no longer exists + can be written from scratch even if FieldWitness no longer exists ### The ALGORITHMS.txt file @@ -143,7 +143,7 @@ This file documents every cryptographic algorithm, parameter, and format used: - **Hashing**: SHA-256 for content and chain linkage; pHash and dHash for perceptual image matching - **Encryption (key bundle)**: AES-256-GCM with Argon2id key derivation (time_cost=4, memory_cost=256MB, parallelism=4) - **Chain format**: Append-only binary log with uint32 BE length prefixes and CBOR (RFC 8949) records -- **Attestation log**: Verisoo binary log format +- **Attestation log**: Attest binary log format --- @@ -163,7 +163,7 @@ are part of an unbroken chain without seeing the contents of other records. ```bash # Disclose records at chain indices 5, 12, and 47 -$ soosef chain disclose --indices 5,12,47 --output disclosure.json +$ fieldwitness chain disclose --indices 5,12,47 --output disclosure.json ``` ### Disclosure output format @@ -180,7 +180,7 @@ $ soosef chain disclose --indices 5,12,47 --output disclosure.json { "chain_index": 5, "content_hash": "...", - "content_type": "verisoo/attestation-v1", + "content_type": "attest/attestation-v1", "prev_hash": "...", "record_hash": "...", "signer_pubkey": "...", @@ -230,11 +230,11 @@ preceded it, because the chain is append-only with hash linkage. If the device has internet access (even temporarily): ```bash -$ soosef chain anchor --tsa https://freetsa.org/tsr +$ fieldwitness chain anchor --tsa https://freetsa.org/tsr ``` This sends the chain head digest to a Timestamping Authority, receives a signed timestamp -token, and saves both as a JSON file under `~/.soosef/chain/anchors/`. The TSA token is a +token, and saves both as a JSON file under `~/.fwmetadata/chain/anchors/`. The TSA token is a cryptographically signed proof from a third party that the hash existed at the stated time. This is legally stronger than a self-asserted timestamp. @@ -243,7 +243,7 @@ This is legally stronger than a self-asserted timestamp. Without `--tsa`: ```bash -$ soosef chain anchor +$ fieldwitness chain anchor ``` This prints a compact text block. Publish it to any external witness: @@ -268,16 +268,16 @@ This prints a compact text block. Publish it to any external witness: For responding to a court order, subpoena, or legal discovery request: -1. **Selective disclosure** (`soosef chain disclose`) when the request specifies particular +1. **Selective disclosure** (`fieldwitness chain disclose`) when the request specifies particular records and you must not reveal the full chain 2. **Evidence package** when the request requires original images with verification capability 3. **Cold archive** when full preservation is required (e.g., an entire investigation) All three formats include standalone verification scripts so the receiving party does not -need SooSeF installed. The verification scripts require only Python 3.11+ and the +need FieldWitness installed. The verification scripts require only Python 3.11+ and the `cryptography` pip package. -> **Note:** Consult with legal counsel before producing evidence from SooSeF. The selective +> **Note:** Consult with legal counsel before producing evidence from FieldWitness. The selective > disclosure mechanism is designed to support legal privilege and proportionality, but its > application depends on your jurisdiction and the specific legal context. diff --git a/docs/federation.md b/docs/federation.md index f45895c..675669d 100644 --- a/docs/federation.md +++ b/docs/federation.md @@ -1,16 +1,16 @@ # Federation Guide **Audience**: System administrators and technical leads setting up cross-organization -attestation sync between SooSeF instances. +attestation sync between FieldWitness instances. -**Prerequisites**: A running SooSeF instance (Tier 2 org server or Tier 3 relay), familiarity +**Prerequisites**: A running FieldWitness instance (Tier 2 org server or Tier 3 relay), familiarity with the CLI, and trusted public keys from partner organizations. --- ## Overview -SooSeF federation synchronizes attestation records between organizations using a gossip +FieldWitness federation synchronizes attestation records between organizations using a gossip protocol. Nodes periodically exchange Merkle roots, detect divergence, and fetch missing records. The system is eventually consistent with no central coordinator, no leader election, and no consensus protocol -- just append-only logs that converge. @@ -20,7 +20,7 @@ Federation operates at two levels: 1. **Offline bundles** -- JSON export/import via sneakernet (USB drive). Works on all tiers including fully airgapped Tier 1 field devices. 2. **Live gossip** -- HTTP-based periodic sync between Tier 2 org servers and Tier 3 - federation relays. Requires the `federation` extra (`pip install soosef[federation]`). + federation relays. Requires the `federation` extra (`pip install fieldwitness[federation]`). > **Warning:** Federation shares attestation records (image hashes, Ed25519 signatures, > timestamps, and signer public keys). It never shares encryption keys, plaintext messages, @@ -76,16 +76,16 @@ Each peer tracks: Unhealthy peers are skipped during gossip rounds but remain registered. They are retried on the next full gossip round. Peer state persists in SQLite at -`~/.soosef/attestations/federation/peers.db`. +`~/.fwmetadata/attestations/federation/peers.db`. ### Gossip interval -The default gossip interval is 60 seconds, configurable via the `VERISOO_GOSSIP_INTERVAL` +The default gossip interval is 60 seconds, configurable via the `FIELDWITNESS_GOSSIP_INTERVAL` environment variable. In Docker Compose, set it in the environment section: ```yaml environment: - - VERISOO_GOSSIP_INTERVAL=60 + - FIELDWITNESS_GOSSIP_INTERVAL=60 ``` Lower intervals mean faster convergence but more network traffic. @@ -102,13 +102,13 @@ Always verify fingerprints out-of-band (in person or over a known-secure voice c On Organization A: ```bash -$ cp ~/.soosef/identity/public.pem /media/usb/org-a-pubkey.pem +$ cp ~/.fwmetadata/identity/public.pem /media/usb/org-a-pubkey.pem ``` On Organization B: ```bash -$ soosef keys trust --import /media/usb/org-a-pubkey.pem +$ fieldwitness keys trust --import /media/usb/org-a-pubkey.pem ``` Repeat in both directions so each organization trusts the other. @@ -123,7 +123,7 @@ Through the web UI at `/federation`, or via the peer store directly: ```bash # On Org A's server, register Org B's federation endpoint -$ soosef federation peer add \ +$ fieldwitness federation peer add \ --url https://orgb.example.org:8000 \ --fingerprint a1b2c3d4e5f6... ``` @@ -143,7 +143,7 @@ security groups, etc.). For manual one-time sync: ```bash -$ soosef federation sync --peer https://orgb.example.org:8000 +$ fieldwitness federation sync --peer https://orgb.example.org:8000 ``` ### Step 4: Monitor sync status @@ -163,19 +163,19 @@ For Tier 1 field devices and airgapped environments, use offline bundles. ### Exporting a bundle ```bash -$ soosef chain export --output /media/usb/bundle.zip +$ fieldwitness chain export --output /media/usb/bundle.zip ``` To export only records from a specific investigation: ```bash -$ soosef chain export --investigation "case-2026-001" --output /media/usb/bundle.zip +$ fieldwitness chain export --investigation "case-2026-001" --output /media/usb/bundle.zip ``` To export a specific index range: ```bash -$ soosef chain export --start 100 --end 200 --output /media/usb/partial.zip +$ fieldwitness chain export --start 100 --end 200 --output /media/usb/partial.zip ``` ### Importing a bundle @@ -183,7 +183,7 @@ $ soosef chain export --start 100 --end 200 --output /media/usb/partial.zip On the receiving instance: ```bash -$ soosef chain import /media/usb/bundle.zip +$ fieldwitness chain import /media/usb/bundle.zip ``` During import: @@ -191,12 +191,12 @@ During import: - Records signed by untrusted fingerprints are rejected - Duplicate records (matching SHA-256) are skipped - Imported records are tagged with `federated_from` metadata -- A delivery acknowledgment record (`soosef/delivery-ack-v1`) is automatically appended +- A delivery acknowledgment record (`fieldwitness/delivery-ack-v1`) is automatically appended to the local chain ### Delivery acknowledgments -When a bundle is imported, SooSeF signs a `soosef/delivery-ack-v1` chain record that +When a bundle is imported, FieldWitness signs a `fieldwitness/delivery-ack-v1` chain record that contains: - The SHA-256 of the imported bundle file @@ -208,7 +208,7 @@ bundle was delivered and ingested. It creates a two-way federation handshake. ```bash # On receiving org: export the acknowledgment back -$ soosef chain export --start --end \ +$ fieldwitness chain export --start --end \ --output /media/usb/delivery-ack.zip ``` @@ -293,7 +293,7 @@ Records are rejected if the signer's fingerprint is not in the local trust store the sender's public key first: ```bash -$ soosef keys trust --import /path/to/sender-pubkey.pem +$ fieldwitness keys trust --import /path/to/sender-pubkey.pem ``` **Consistency proof failure** @@ -302,7 +302,7 @@ A consistency proof failure means the peer's log is not a valid extension of the This indicates a potential fork -- the peer may have a different chain history. Investigate before proceeding: -1. Compare chain heads: `soosef chain status` on both instances +1. Compare chain heads: `fieldwitness chain status` on both instances 2. If a fork is confirmed, one instance's records must be exported and re-imported into a fresh chain @@ -311,7 +311,7 @@ before proceeding: The gossip loop requires the `federation` extra: ```bash -$ pip install "soosef[federation]" +$ pip install "fieldwitness[federation]" ``` This installs `aiohttp` for async HTTP communication. diff --git a/docs/index.md b/docs/index.md index 90b7968..d1e3c78 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# SooSeF Documentation +# FieldWitness Documentation ## For Reporters and Field Users @@ -30,5 +30,5 @@ |---|---| | [Federation Architecture](architecture/federation.md) | System design: threat model, layers (chain, bundles, federation), key domains, permission tiers. | | [Chain Format Spec](architecture/chain-format.md) | CBOR record format, entropy witnesses, serialization, storage format, content types. | -| [Export Bundle Spec](architecture/export-bundle.md) | SOOSEFX1 binary format, envelope encryption (X25519 + AES-256-GCM), Merkle trees. | +| [Export Bundle Spec](architecture/export-bundle.md) | FIELDWITNESSX1 binary format, envelope encryption (X25519 + AES-256-GCM), Merkle trees. | | [Federation Protocol Spec](architecture/federation-protocol.md) | CT-inspired server protocol: API endpoints, gossip, storage tiers, receipts, security model. | diff --git a/docs/planning/c2pa-integration.md b/docs/planning/c2pa-integration.md new file mode 100644 index 0000000..0c2e4bc --- /dev/null +++ b/docs/planning/c2pa-integration.md @@ -0,0 +1,240 @@ +# C2PA Integration Plan + +**Audience:** FieldWitness developers and maintainers +**Status:** Planning (pre-implementation) +**Last updated:** 2026-04-01 + +## Overview + +FieldWitness needs C2PA (Coalition for Content Provenance and Authenticity) export/import +capability. C2PA is the emerging industry standard for content provenance, backed by +Adobe, Microsoft, Google, and the BBC. ProofMode, Guardian Project, and Starling Lab +have all adopted C2PA. FieldWitness must speak C2PA to remain relevant in the provenance +space. + +--- + +## C2PA Spec Essentials + +- JUMBF-based provenance standard embedded in media files +- Core structures: **Manifest Store > Manifest > Claim + Assertions + Ingredients + Signature** +- Claims are CBOR maps with assertion references, signing algorithm, `claim_generator`, + and timestamps +- Standard assertions: + - `c2pa.actions` -- edit history + - `c2pa.hash.data` -- hard binding (byte-range) + - `c2pa.location.broad` -- city/region location + - `c2pa.exif` -- EXIF metadata + - `c2pa.creative.work` -- title, description, authorship + - `c2pa.training-mining` -- AI training/mining consent + - Vendor-specific assertions under reverse-DNS (e.g., `org.fieldwitness.*`) +- Signing uses **COSE_Sign1** (RFC 9052) + - Supported algorithms: Ed25519 (OKP), ES256/ES384/ES512 (ECDSA), PS256/PS384/PS512 (RSA-PSS) +- **X.509 certificate chain required** -- embedded in COSE unprotected header; raw public + keys are not sufficient +- Offline validation works with pre-installed trust anchors; self-signed certs work in + "local trust anchor" mode + +## Python Library: c2pa-python + +- Canonical binding from C2PA org (PyPI: `c2pa-python`, GitHub: `contentauth/c2pa-python`) +- Rust extension (`c2pa-rs` via PyO3), not pure Python +- Version ~0.6.x, API not fully stable +- Platform wheels: manylinux2014 x86_64/aarch64, macOS, Windows + - **No armv6/armv7 wheels** -- affects Tier 1 Raspberry Pi deployments +- Core API: `c2pa.Reader`, `c2pa.Builder`, `builder.sign()`, `c2pa.create_signer()` +- `create_signer` takes a callback, algorithm, certs PEM, optional timestamp URL +- `timestamp_url=None` skips RFC 3161 timestamping (acceptable for offline use) + +--- + +## Concept Mapping: FieldWitness to C2PA + +### Clean mappings + +| FieldWitness | C2PA | +|--------|------| +| `AttestationRecord` | C2PA Manifest | +| `attestor_fingerprint` | Signer cert subject (wrapped in X.509) | +| `AttestationRecord.timestamp` | Claim `created` (ISO 8601) | +| `CaptureMetadata.captured_at` | `c2pa.exif` DateTimeOriginal | +| `CaptureMetadata.location` | `c2pa.location.broad` | +| `CaptureMetadata.device` | `c2pa.exif` Make/Model | +| `CaptureMetadata.caption` | `c2pa.creative.work` description | +| `ImageHashes.sha256` | `c2pa.hash.data` (hard binding) | +| Ed25519 private key | COSE_Sign1 signing key (needs X.509 wrapper) | + +### FieldWitness has, C2PA does not + +- Perceptual hashes (phash, dhash) -- map to vendor assertion `org.fieldwitness.perceptual-hashes` +- Merkle log inclusion proofs -- map to vendor assertion `org.fieldwitness.merkle-proof` +- Chain records with entropy witnesses -- map to vendor assertion `org.fieldwitness.chain-record` +- Delivery acknowledgment records (entirely FieldWitness-specific) +- Cross-org gossip federation +- Perceptual matching for verification (survives recompression) +- Selective disclosure / redaction + +### C2PA has, FieldWitness does not + +- Hard file binding (byte-range exclusion zones) +- X.509 certificate trust chains +- Actions history (`c2pa.actions`: crop, rotate, AI-generate, etc.) +- AI training/mining consent +- Ingredient DAG (content derivation graph) + +--- + +## Privacy Design + +Three tiers of identity disclosure: + +1. **Org-level cert (preferred):** One self-signed X.509 cert per organization, not per + person. Subject is org name. Individual reporters do not appear in the manifest. + +2. **Pseudonym cert:** Subject is pseudonym or random UUID. Valid C2PA but unrecognized + by external trust anchors. + +3. **No C2PA export:** For critical-threat presets, evidence stays in FieldWitness format until + reaching Tier 2. + +### GPS handling + +C2PA's `c2pa.location.broad` is city/region level. FieldWitness captures precise GPS. On +export, downsample to city-level unless the operator explicitly opts in. Precise GPS +stays in FieldWitness record only. + +### Metadata handling + +Strip all EXIF from the output file except what is intentionally placed in the +`c2pa.exif` assertion. + +--- + +## Offline-First Constraints + +- **Tier 1 (field, no internet):** C2PA manifests without RFC 3161 timestamp. FieldWitness + chain record provides timestamp anchoring via vendor assertion. +- **Tier 2 (org server, may have internet):** Optionally contact TSA at export time. + Connects to existing `anchors.py` infrastructure. +- Entropy witnesses embedded as vendor assertions provide soft timestamp evidence. +- Evidence packages include org cert PEM alongside C2PA manifest for offline verification. +- `c2pa-python` availability gated behind `has_c2pa()` -- not all hardware can run it. + +--- + +## Architecture + +### New module: `src/fieldwitness/c2pa_bridge/` + +``` +src/fieldwitness/c2pa_bridge/ + __init__.py # Public API: export, import, has_c2pa() + cert.py # Self-signed X.509 cert generation from Ed25519 key + export.py # AttestationRecord -> C2PA manifest + importer.py # C2PA manifest -> AttestationRecord (best-effort) + vendor_assertions.py # org.fieldwitness.* assertion schemas + cli.py # CLI subcommands: fieldwitness c2pa export / verify / import +``` + +### Module relationships + +- `export.py` reads from `attest/models.py`, `federation/chain.py`, + `keystore/manager.py`; calls `cert.py` and `vendor_assertions.py` +- `importer.py` reads image bytes, writes `AttestationRecord` via + `attest/attestation.py`, parses vendor assertions + +### Web UI + +New routes in the `attest.py` blueprint: +- `GET /attest//c2pa` -- download C2PA-embedded image +- `POST /attest/import-c2pa` -- upload and import C2PA manifest + +### Evidence packages + +`evidence.py` gains `include_c2pa=True` option. Adds C2PA-embedded file variants and +org cert to the ZIP. + +### pyproject.toml extra + +```toml +c2pa = ["c2pa-python>=0.6.0", "fieldwitness[attest]"] +``` + +--- + +## Implementation Phases + +### Phase 0 -- Prerequisites (~1h) + +- `has_c2pa()` in `_availability.py` +- `c2pa` extra in `pyproject.toml` + +### Phase 1 -- Certificate management (~3h) + +- `c2pa_bridge/cert.py` +- Self-signed X.509 from Ed25519 identity key +- Configurable subject (org name default, pseudonym for high-threat) +- Store at `~/.fwmetadata/identity/c2pa_cert.pem` +- Regenerate on key rotation + +### Phase 2 -- Export path (~6h) + +- `c2pa_bridge/export.py` + `vendor_assertions.py` +- Core function `export_c2pa()` takes image data, `AttestationRecord`, key, cert, options +- Builds assertions: `c2pa.actions`, `c2pa.hash.data`, `c2pa.exif`, `c2pa.creative.work`, + `org.fieldwitness.perceptual-hashes`, `org.fieldwitness.chain-record`, `org.fieldwitness.attestation-id` +- Vendor assertion schemas versioned (v1) + +### Phase 3 -- Import path (~5h) + +- `c2pa_bridge/importer.py` +- `import_c2pa()` reads C2PA manifest, produces `AttestationRecord` +- Maps C2PA fields to FieldWitness model +- Returns `C2PAImportResult` with `trust_status` +- Creates new FieldWitness attestation record over imported data + +### Phase 4 -- CLI integration (~4h) + +- `fieldwitness c2pa export/verify/import/show` subcommands +- Gated on `has_c2pa()` + +### Phase 5 -- Web UI + evidence packages (~5h) + +- Blueprint routes for export/import +- Evidence package C2PA option + +### Phase 6 -- Threat-level presets (~2h) + +- Add `c2pa` config block to each preset (`export_enabled`, `privacy_level`, + `include_precise_gps`, `timestamp_url`) +- `C2PAConfig` sub-dataclass in `FieldWitnessConfig` + +### MVP scope + +**Phases 0-2 (~10h):** Produces C2PA-compatible images viewable in Adobe Content +Credentials and any C2PA verifier. + +--- + +## Key Decisions (Before Coding) + +1. **Use existing Ed25519 identity key for cert** (not a separate key) -- preserves + single-key-domain design. +2. **Cert stored at `~/.fwmetadata/identity/c2pa_cert.pem`**, regenerated on key rotation. +3. **Tier 1 ARM fallback:** Tier 1 produces FieldWitness records; Tier 2 generates C2PA export + on their behalf. +4. **Pin `c2pa-python>=0.6.0`**, add shim layer for API stability. +5. **Hard binding computed by `c2pa-python` Builder** automatically. + +--- + +## FieldWitness's Unique C2PA Value + +- **Cross-org chain of custody** via gossip federation (delivery ack records as ingredients) +- **Perceptual hash matching** embedded in C2PA (survives JPEG recompression via + WhatsApp/Telegram) +- **Merkle log inclusion proofs** in manifest (proves attestation committed to append-only log) +- **Entropy witnesses** as soft timestamp attestation (makes backdating harder without + RFC 3161) +- **Privacy-preserving by design** (org certs, GPS downsampling, zero-identity mode) +- **Fully offline end-to-end verification** (bundled cert + `c2pa-python`, no network needed) diff --git a/docs/planning/gtm-feasibility.md b/docs/planning/gtm-feasibility.md new file mode 100644 index 0000000..d60c806 --- /dev/null +++ b/docs/planning/gtm-feasibility.md @@ -0,0 +1,214 @@ +# Go-to-Market Feasibility Plan + +**Audience:** Internal planning (solo developer) +**Status:** Active planning document +**Last updated:** 2026-04-01 + +## Overview + +Phased plan for building credibility and visibility for FieldWitness in the press freedom and +digital security space. Constraints: solo developer, ~10-15 hrs/week, portfolio/learning +project that should also produce real-world value. + +--- + +## Current Strengths + +- Federation layer is genuinely novel: gossip-based attestation sync across orgs with + offline-first design and append-only hash chains +- Three-tier deployment model maps to how press freedom orgs actually work +- C2PA export is well-timed as CAI gains momentum +- Working codebase with tests, deployment configs, documentation + +## Core Challenges + +- **Trust deficit:** "Some guy built a tool" is a warning sign in this space, not a + selling point +- **Chicken-and-egg:** Need audit for credibility, need credibility/money for audit, + need adoption for money +- **Limited bandwidth:** 10-15 hrs/week makes sequencing critical +- **Stego perception risk:** Steganography angle can be a credibility liability if + positioned as headline feature (perceived as "hacker toy") + +--- + +## Phase 1: Foundation (Months 1-6) + +**Goal:** Make the project legible to the ecosystem. + +### Technical credibility (60% of time) + +- Ship C2PA export as v0.3.0 headline feature (target: 8 weeks) +- Write formal threat model document at `docs/security/threat-model.md` + - Model after Signal protocol docs or Tor design doc +- De-emphasize steganography in public surfaces -- lead with "offline-first provenance + attestation with gossip federation" +- Set up reproducible builds with pinned dependencies +- Get CI/CD visibly working with test/lint/type-check/coverage badges + +### Positioning and documentation (20% of time) + +- Write "Why FieldWitness Exists" document (~1500 words): the problem, why existing tools + don't solve it, what FieldWitness does differently, who it's for, what it needs +- Create 2-minute demo video: field attestation -> sneakernet sync -> federation -> + verification + +### Community engagement (20% of time) + +- Lurk on `liberationtech@lists.stanford.edu` -- do NOT announce tool cold; wait for + relevant threads +- GitHub engagement with adjacent projects (real contributions, not performative): + - `guardian/proofmode-android` + - `contentauth/c2pa-python` + - `freedomofpress/securedrop` +- Post Show HN when C2PA export ships + +--- + +## Phase 2: Credibility Escalation (Months 7-12) + +**Goal:** Get external validation from at least one recognized entity. + +### OTF (Open Technology Fund) -- https://www.opentech.fund/ + +**Internet Freedom Fund:** $50K-$900K over 12-36 months. Solo developers eligible. +Rolling applications. + +**Red Team Lab:** FREE security audits commissioned through partner firms (Cure53, Trail +of Bits, Radically Open Security). This is the single highest-leverage action. + +**Usability Lab:** Free UX review. + +**Application timeline:** 2-4 months from submission to decision. + +**Strategy:** Apply to Red Team Lab for audit FIRST (lower commitment for OTF, validates +you as "OTF-vetted"). + +### Compelling application elements + +1. Lead with problem: "Provenance attestation tools assume persistent internet. For + journalists in [specific scenario], this fails." +2. Lead with differentiator: "Gossip federation for cross-org attestation sync, + offline-first, bridges to C2PA." +3. Be honest about status: "Working prototype at v0.3.0, needs audit and field testing." +4. Budget: stipend, audit (if Red Team Lab unavailable), 1-2 conferences, federation + relay hosting. + +### Backup audit and funding paths + +| Organization | URL | Notes | +|---|---|---| +| OSTIF | https://ostif.org/ | Funds audits for open-source projects; may be too early-stage | +| Radically Open Security | https://www.radicallyopensecurity.com/ | Nonprofit, reduced rates for internet freedom projects; focused audit ~$15-30K | +| NLnet Foundation | https://nlnet.nl/ | EUR 5-50K grants, lightweight process, solo devs welcome, includes audit funding | +| Filecoin Foundation for Decentralized Web | https://fil.org/grants | Relevant to federation/provenance angle | + +### Community building + +- Submit talk to **IFF 2027** (Internet Freedom Festival, Valencia, ~March) + - Open sessions and tool showcases have low barriers + - Talk title: "Federated Evidence Chains: Offline Provenance for Journalists in + Hostile Environments" +- Cold outreach to 3-5 specific people: + - Access Now Digital Security Helpline trainers + - Harlo Holmes (FPF Director of Digital Security) + - Guardian Project developers (ProofMode team) + - Position as complementary, not competitive + - Lead with "I want honest feedback" +- Conferences: + - **RightsCon** -- https://www.rightscon.org/ + - **IFF** -- https://internetfreedomfestival.org/ + - **USENIX Security / PETS** -- academic venues, for federation protocol paper + +--- + +## Phase 3: Traction or Pivot (Months 13-24) + +### Green lights (keep going) + +- OTF Red Team Lab acceptance or any grant funding +- A digital security trainer says "I could see using this" +- A journalist or NGO runs it in any scenario +- Another developer contributes a meaningful PR +- Conference talk accepted + +### Red lights (pivot positioning) + +- Zero response from outreach after 6+ months +- Funders say problem is already solved +- Security reviewers find fundamental design flaws + +### If green (months 13-24) + +- Execute audit, publish results publicly (radical transparency) +- Build pilot deployment guide +- Apply for Internet Freedom Fund +- Present at RightsCon 2027/2028 + +### If red (months 13-24) + +- Reposition as reference implementation / research project +- Write federation protocol as academic paper +- Lean into portfolio angle + +--- + +## Professional Portfolio Positioning + +### Framing + +"I designed and implemented a gossip-based federation protocol for offline-first +provenance attestation, targeting field deployment in resource-constrained environments. +The system uses Ed25519 signing, Merkle trees with consistency proofs, append-only hash +chains with CBOR serialization, and bridges to the C2PA industry standard." + +### Skills demonstrated + +- Cryptographic protocol design +- Distributed systems (gossip, consistency proofs) +- Security engineering (threat modeling, audit prep, key management) +- Systems architecture (three-tier, offline-first) +- Domain expertise (press freedom, evidence integrity) +- Grant writing (if pursued) + +### Target roles + +- Security engineer (FPF, EFF, Access Now, Signal, Cloudflare) +- Protocol engineer (decentralized systems) +- Developer advocate (security companies) +- Infrastructure engineer + +### Key portfolio artifacts + +- Threat model document (shows security thinking) +- Audit report, even with findings (shows maturity) +- C2PA bridge (shows standards interop, not just NIH) + +--- + +## Timeline (10-15 hrs/week) + +| Month | Focus | Deliverable | Time split | +|-------|-------|-------------|------------| +| 1-2 | C2PA export + threat model | v0.3.0, `threat-model.md` | 12 code, 3 docs | +| 3-4 | Demo video + "Why FieldWitness" + CI | Video, doc, badges | 8 code, 4 docs, 3 outreach | +| 5-6 | OTF Red Team Lab app + community | Application submitted, Show HN | 5 code, 5 grants, 5 outreach | +| 7-9 | Community + backup grants | Outreach emails, NLnet/FFDW apps | 8 code, 3 grants, 4 outreach | +| 10-12 | IFF submission + traction check | Talk submitted, go/no-go decision | 8 code, 2 grants, 5 outreach | +| 13-18 | (If green) Audit + pilot guide | Published audit, pilot doc | 10 code, 5 docs | +| 19-24 | (If green) Conference + IFF app | Talk, major grant application | 5 code, 5 grant, 5 outreach | + +--- + +## What NOT to Bother With + +- Paid marketing, ads, PR +- Product Hunt, startup directories, "launch" campaigns +- Project website beyond clean README +- Corporate partnerships +- Whitepapers before audit +- Mobile apps +- Discord/Slack community (dead community is worse than none) +- Press coverage (too early) +- Competing with SecureDrop on source protection +- General tech conference talks (domain-specific venues only) diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md new file mode 100644 index 0000000..7a7442e --- /dev/null +++ b/docs/security/threat-model.md @@ -0,0 +1,480 @@ +# FieldWitness Threat Model + +**Status:** Living document -- updated as the design evolves and as external review +identifies gaps. Version numbers track significant revisions. + +**Document version:** 0.1 (2026-04-01) +**Corresponds to:** FieldWitness v0.2.0 + +This document follows the style of the Signal Protocol specification and the Tor design +document: it makes precise claims, distinguishes what is guaranteed from what is not, and +does not use marketing language. Unresolved questions and known gaps are stated plainly. + +**This document has not been externally audited.** Claims here reflect the designer's +intent and analysis. An independent security review is planned as part of Phase 2 (see +`docs/planning/gtm-feasibility.md`). Until that review is complete, treat this document +as a design statement, not a security certification. + +--- + +## Table of Contents + +1. [Intended Users](#1-intended-users) +2. [Adversary Model](#2-adversary-model) +3. [Assets Protected](#3-assets-protected) +4. [Trust Boundaries](#4-trust-boundaries) +5. [Security Guarantees](#5-security-guarantees) +6. [Non-Guarantees](#6-non-guarantees) +7. [Cryptographic Primitives](#7-cryptographic-primitives) +8. [Key Management Model](#8-key-management-model) +9. [Federation Trust Model](#9-federation-trust-model) +10. [Known Limitations](#10-known-limitations) + +--- + +## 1. Intended Users + +FieldWitness is designed for three overlapping user populations: + +**Field reporters and documenters.** Journalists, human rights monitors, and election +observers working in environments where physical device seizure is a plausible risk. +Operating assumption: the user may be detained, the device may be confiscated, and the +operator at Tier 2 (the org server) may not be reachable in real time. The user needs to +attest evidence locally on a Tier 1 (field device) and sync later -- or never, if the USB +is destroyed. + +**Organizational administrators.** IT staff and security-aware operators at newsrooms or +NGOs running Tier 2 deployments. They manage keys, configure threat levels, operate the +source drop box, and maintain federation peering with partner organizations. They are +expected to understand basic operational security concepts but are not expected to be +cryptographers. + +**Partner organizations.** Organizations that receive attested evidence bundles from the +primary organization and need to verify chain-of-custody without installing FieldWitness. They +interact with standalone `verify.py` scripts included in evidence packages. + +FieldWitness is **not** designed as a general-purpose secure communications tool, a replacement +for SecureDrop's source protection model, or a consumer privacy application. + +--- + +## 2. Adversary Model + +### 2.1 Passive Network Observer + +**Capability:** Can observe all network traffic between nodes, including Tier 2 to Tier 3 +communication and gossip federation traffic. Cannot break TLS or Ed25519. + +**Goal:** Determine which organizations are communicating, the timing and volume of +attestation syncs, and potentially correlate sync events with news events. + +**FieldWitness's position:** Transport-level metadata (IP addresses, timing, volume) is not +hidden. TLS (self-signed, port 8000) protects payload content. A passive observer can +determine that two Tier 2 servers are federating; they cannot read the attestation records +being exchanged without the relevant Ed25519 public keys. + +**Gap:** No traffic padding, no onion routing, no anonymization of federation topology. +Organizations with strong network-level adversaries should route federation traffic through +Tor or a VPN. This is not built in. + +### 2.2 Active Network Adversary + +**Capability:** Can intercept, modify, replay, and drop traffic. Can present forged +TLS certificates if the operator hasn't pinned the peer's certificate. + +**Goal:** Inject forged attestation records into the federation, suppress legitimate +records, or cause evidence to appear tampered. + +**FieldWitness's position:** All attestation records are Ed25519-signed. A network adversary +cannot forge a valid signature without the private key. The append-only hash chain makes +retroactive injection detectable: inserting a record at position N requires recomputing all +subsequent hashes. Consistency proofs during gossip sync detect log divergence. + +**Gap:** Certificate pinning for federation peers is not implemented as of v0.2.0. The +Tier 3 relay uses a self-signed certificate; operators should verify its fingerprint +out-of-band. Gossip peers authenticate by Ed25519 fingerprint, not certificate, which +provides a secondary check. + +### 2.3 Physical Access + +**Capability:** Has physical access to the field device (Tier 1 USB) or the org server +(Tier 2). May have forensic tools. + +**Goal:** Extract private keys, recover attested evidence, identify the operator, or +determine what evidence was collected. + +**FieldWitness's position:** + +- Tier 1 is designed for amnesia: Debian Live USB with LUKS-encrypted persistent + partition. Pulling the USB from the host leaves no trace on the host machine. If the USB + itself is seized, LUKS protects the persistent partition. +- The killswitch (`fieldwitness fieldkit purge`) destroys all key material and data under + `~/.fwmetadata/` in sensitivity order. The deep forensic scrub removes Python bytecache, pip + metadata, download cache, and shell history entries. The final step is `pip uninstall + -y fieldwitness`. +- The dead man's switch fires the killswitch automatically if check-in is missed. +- Private keys are stored as PEM files with `0600` permissions. Key material is not + additionally encrypted at rest beyond the filesystem (LUKS on Tier 1; operator-managed + on Tier 2). + +**Gap:** If the device is seized before the killswitch fires and LUKS has been unlocked +(i.e., the device is running), private keys are accessible. Cold boot attacks against +unlocked LUKS volumes are not mitigated. Key material is not stored in a hardware security +module or OS keychain. + +### 2.4 Legal Compulsion + +**Capability:** Can compel the operator (or their legal jurisdiction) to produce data, +keys, or records. May use court orders, search warrants, or jurisdiction-specific +administrative processes. + +**Goal:** Obtain attestation records or private keys under legal authority. + +**FieldWitness's position:** FieldWitness provides tools (selective disclosure, evidence packages) +for producing specific records under court order without revealing the full chain. +The Federation Relay (Tier 3) stores only hashes and signatures -- never private keys or +plaintext. Placing Tier 3 in a jurisdiction with strong press protections limits one +compulsion surface. + +**Gap:** If private keys are seized, all past and future attestations signed by those keys +are attributable to the key holder. Key rotation limits forward exposure after a +compromise, but prior records signed by the old key remain attributable. FieldWitness does not +implement deniable authentication. + +### 2.5 Insider Threat + +**Capability:** Has legitimate access to the FieldWitness instance (e.g., a trusted +administrator or a compromised org server). Can read key material, attestation records, +and logs. + +**Goal:** Selectively alter or delete records, export keys, or suppress evidence. + +**FieldWitness's position:** The append-only hash chain makes deletion or modification of prior +records detectable: the chain head hash changes and any external anchor (RFC 3161 TSA, +blockchain transaction) will no longer match. Key rotation is logged in the chain as a +`fieldwitness/key-rotation-v1` record signed by the old key, creating an auditable trail. + +**Gap:** An insider with direct filesystem access can overwrite `chain.bin` entirely, +including the chain head, before an external anchor is taken. The chain provides integrity +guarantees only to the extent that external anchors are taken regularly and independently +(by another party or a public TSA). Frequency of anchoring is an operational decision, not +enforced by the software. + +--- + +## 3. Assets Protected + +The following assets are in scope for FieldWitness's security model: + +| Asset | Description | Primary Protection | +|---|---|---| +| Attestation records | Ed25519-signed records linking a file hash to a time, identity, and optional metadata | Append-only chain, Ed25519 signatures | +| Identity private key | Ed25519 private key used to sign attestations | `0600` filesystem permissions, LUKS on Tier 1, killswitch | +| Channel key | AES-256-GCM key used for steganographic encoding | `0600` filesystem permissions, separate from identity key | +| Source submissions | Anonymous uploads through the drop box | EXIF stripping, no-branding upload page, HMAC receipt codes | +| Evidentiary metadata | GPS, timestamp, device model extracted from EXIF | Stored in attestation record, dangerous fields stripped | +| Federation topology | Which organizations are peering | Not protected at network level (see 2.1) | + +The following are **out of scope** (not protected by FieldWitness): + +- Source identity beyond what is stripped from EXIF +- Operator identity (FieldWitness does not provide anonymity) +- Content of files beyond what is hashed and signed (files are not encrypted at rest + unless encrypted before attestation) +- The Tier 3 relay's knowledge of federation topology + +--- + +## 4. Trust Boundaries + +``` +[Field reporter / Tier 1] --- LUKS + killswitch --- [Seized device adversary] + | + [USB sneakernet or LAN] + | +[Org Server / Tier 2] ------- TLS (self-signed) ---- [Network adversary] + | [Active attacker: no forged sigs] + [Gossip federation] + | +[Federation Relay / Tier 3] - Stores hashes+sigs only, no keys + | +[Partner Org / Tier 2] ------ Ed25519 trust store -- [Untrusted peers rejected] + | +[Verifying party] ----------- standalone verify.py, cryptography package only +``` + +**Tier 1 trusts:** Its own key material (generated locally), the Tier 2 server it was +configured to sync with. + +**Tier 2 trusts:** Its own key material, the Ed25519 public keys in its trust store +(imported explicitly by the administrator), the Tier 3 relay for transport only (not +content validation). + +**Tier 3 trusts:** Nothing. It is a content-unaware relay. It cannot validate the +semantic content of what it stores because it has no access to private keys. + +**Verifying party trusts:** The signer's Ed25519 public key (received out-of-band, e.g., +in the evidence package), the `cryptography` Python package, and the chain linkage logic +in `verify.py`. + +--- + +## 5. Security Guarantees + +The following are properties FieldWitness is designed to provide. Each is conditional on the +named preconditions. + +**G1: Attestation integrity.** Given an attestation record and the signer's Ed25519 public +key, a verifier can determine whether the record has been modified since signing. +_Precondition:_ The verifier has the correct public key and the `cryptography` package. +_Mechanism:_ Ed25519 signature over deterministic JSON serialization of the record. + +**G2: Chain append-only property.** If a record is in the chain at position N, it cannot +be removed or modified without invalidating every subsequent record's hash linkage. +_Precondition:_ The verifier has observed the chain head at some prior point or has an +external anchor. +_Mechanism:_ Each record includes `prev_hash = SHA-256(canonical_bytes(record[N-1]))`. + +**G3: Timestamp lower bound.** If the chain head has been submitted to an RFC 3161 TSA +and the token is preserved, all records prior to the anchored head provably existed before +the TSA's signing time. +_Precondition:_ The TSA's clock and signing key are trusted. +_Mechanism:_ RFC 3161 timestamp tokens stored in `chain/anchors/`. + +**G4: Selective disclosure soundness.** A selective disclosure bundle proves that the +disclosed records are part of an unbroken chain without revealing the contents of +non-disclosed records. +_Precondition:_ The verifier has the chain head hash from an external source. +_Mechanism:_ Non-selected records appear as hashes only; chain linkage is preserved. + +**G5: Federation record authenticity.** Records received via federation are accepted only +if signed by a key in the local trust store. +_Precondition:_ The trust store contains only keys the operator has explicitly imported. +_Mechanism:_ Ed25519 verification against trust store before appending federated records. + +**G6: Source drop box anonymity (limited).** A source submitting via the drop box does +not need an account, FieldWitness is not mentioned on the upload page, and dangerous EXIF fields +are stripped before the file is stored. +_Precondition:_ The source accesses the drop box URL over HTTPS without revealing their +identity through other means (IP, browser fingerprint, etc.). +_Limitation:_ FieldWitness does not route drop box traffic through Tor or any anonymization +layer. Network-level anonymity is the source's responsibility. + +--- + +## 6. Non-Guarantees + +The following properties are explicitly **not** provided by FieldWitness. Including them here +prevents users from assuming protection that does not exist. + +**NG1: Operator anonymity.** FieldWitness does not hide the identity of the organization +running the instance. The Tier 2 server has an IP address. The federation relay knows +which Tier 2 servers are peering. + +**NG2: Deniable authentication.** Attestation records are non-repudiably signed by +Ed25519 keys. There is no plausible deniability about which key produced a signature. + +**NG3: Forward secrecy for attestation keys.** Ed25519 identity keys are long-lived. +If a private key is compromised, all attestations signed by that key are attributable to +the key holder. Key rotation limits future exposure but does not retroactively remove +attributability. + +**NG4: Protection against a compromised Tier 2 before anchoring.** An insider with full +Tier 2 access can rewrite the chain before any external anchor is taken. External anchors +are the primary protection against insider tampering; their value is proportional to how +frequently and independently they are taken. + +**NG5: Content confidentiality.** FieldWitness does not encrypt attested files at rest. Files +are hashed and signed, not encrypted. Encryption before attestation is the operator's +responsibility. + +**NG6: Source protection beyond EXIF stripping.** The drop box strips dangerous EXIF +fields and does not log source IP addresses in attestation records. It does not provide +the same source protection model as SecureDrop. Organizations with strong source +protection requirements should use SecureDrop for intake and FieldWitness for evidence chain +management. + +**NG7: Auditability of the Tier 3 relay.** The relay stores only hashes and signatures, +but FieldWitness does not currently provide a mechanism for operators to audit what the relay +has and has not forwarded. The relay is trusted for availability, not integrity. + +--- + +## 7. Cryptographic Primitives + +All cryptographic choices are documented here to support independent review and long-term +archival verifiability. + +### Signing + +| Primitive | Algorithm | Parameters | Use | +|---|---|---|---| +| Identity signing | Ed25519 | RFC 8032 | Sign attestation records, key rotation records, delivery acks | +| Key storage | PEM | PKCS8 (private), SubjectPublicKeyInfo (public) | Disk format for identity keypair | + +Ed25519 was chosen for: short key and signature sizes (32-byte public key, 64-byte +signature), deterministic signing (no random oracle required per operation), strong +security margins, and wide library support. + +### Encryption (Stego channel key domain) + +| Primitive | Algorithm | Parameters | Use | +|---|---|---|---| +| Symmetric encryption | AES-256-GCM | 256-bit key, 96-bit IV, 128-bit tag | Payload encryption in stego encode | +| Key derivation | Argon2id | time=3, memory=65536, parallelism=4, saltlen=16 | Derive AES key from passphrase + PIN + reference photo fingerprint | + +**Note:** The AES-256-GCM channel key domain (Stego) and the Ed25519 identity key +domain (Attest) are kept strictly separate. They serve different security purposes and +share no key material. + +### Hashing + +| Primitive | Algorithm | Use | +|---|---|---| +| Cryptographic hash | SHA-256 | Chain record linkage (`prev_hash`), content fingerprinting | +| Content fingerprinting | SHA-256 | `ImageHashes.sha256` for all file types | +| Perceptual hash | pHash (DCT-based) | Image tamper detection, survives compression | +| Perceptual hash | dHash (difference hash) | Image tamper detection | +| Perceptual hash | aHash (average hash) | Fuzzy matching, high tolerance | +| Chain serialization | CBOR (RFC 7049) | Canonical encoding for chain records | +| HMAC | HMAC-SHA256 | Drop box receipt code derivation | + +### External Timestamping + +| Mechanism | Standard | Use | +|---|---|---| +| RFC 3161 TSA | RFC 3161 | Automated, signed timestamp tokens | +| Manual anchor | Any external witness | Chain head hash submitted to blockchain, email, etc. | + +--- + +## 8. Key Management Model + +### Key types and locations + +| Key | Type | Location | Purpose | +|---|---|---|---| +| Identity private key | Ed25519 | `~/.fwmetadata/identity/private.pem` | Sign all attestation records | +| Identity public key | Ed25519 | `~/.fwmetadata/identity/public.pem` | Shared with verifiers; included in evidence packages | +| Channel key | AES-256-GCM | `~/.fwmetadata/stego/channel.key` | Stego encoding/decoding shared secret | +| Trust store keys | Ed25519 (public only) | `~/.fwmetadata/trusted_keys//` | Verify federated records from partners | + +### Key rotation + +Identity rotation creates a `fieldwitness/key-rotation-v1` chain record signed by the **old** +key, containing the new public key. This establishes a cryptographic chain of trust from +the original key through all rotations. Verifiers following the rotation chain can confirm +that new attestations come from the same organizational identity as old ones. + +Channel (AES) key rotation creates a new key and archives the old one. Old channel keys +are required to decode stego payloads encoded with them; archived keys are preserved under +`~/.fwmetadata/stego/archived/`. + +### Identity recovery + +After device loss, a `fieldwitness/key-recovery-v1` chain record is signed by the **new** key, +carrying the old key's fingerprint and optional cosigner fingerprints. This is an +auditable assertion, not a cryptographic proof that the old key authorized the recovery +(the old key is lost). The recovery record's legitimacy depends on out-of-band +confirmation (e.g., cosigner verification, organizational attestation). + +### Backup + +The keystore manager (`fieldwitness/keystore/manager.py`) tracks backup state. Encrypted key +bundles can be exported to the SOOBNDL format for cold storage. The backup reminder +interval is configurable; the default is 7 days. + +--- + +## 9. Federation Trust Model + +### Peer authentication + +Federation peers are identified by their Ed25519 public key fingerprint (first 16 bytes of +SHA-256 of the public key, hex-encoded). Peering is established by explicit administrator +action: the peer's public key fingerprint is configured locally. There is no automatic +peer discovery or trust-on-first-use. + +### Record acceptance + +A record received via federation is accepted only if: +1. It is signed by an Ed25519 key in the local trust store. +2. The signature is valid over the record's canonical serialization. +3. The record does not duplicate a record already in the local log (by record ID). + +Records signed by unknown keys are silently dropped. There is no mechanism to accept +records from temporarily trusted but unregistered peers. + +### The Tier 3 relay + +The Tier 3 relay is a content-unaware intermediary. It forwards attestation bundles between +Tier 2 nodes but has no access to private keys and cannot validate the semantic content of +records. It is trusted for availability (it should forward what it receives) but not for +integrity (it cannot be used as an authority for whether records are authentic). + +### Consistency proofs + +During gossip sync, nodes exchange their current Merkle log root and size. If roots +differ, the node with fewer records requests a consistency proof from the node with more +records. The consistency proof proves that the smaller log is a prefix of the larger log, +preventing log divergence. Records are fetched incrementally after the proof verifies. + +The consistency proof implementation is in `src/fieldwitness/attest/merkle.py`. + +--- + +## 10. Known Limitations + +This section is a candid accounting of current gaps. Items here are candidates for future +work, not dismissals. + +**L1: No hardware key storage.** Private keys are stored as PEM files protected only by +filesystem permissions and LUKS (on Tier 1). A hardware security module (HSM), TPM, or OS +keychain would provide stronger protection against physical extraction from a running +system. This is a significant gap for high-threat deployments. + +**L2: No certificate pinning for federation.** Tier 2 to Tier 3 connections use TLS with +self-signed certificates. The peer's certificate fingerprint is not currently pinned in +configuration. An active network adversary with the ability to present a forged certificate +could intercept federation traffic. The Ed25519 peer fingerprint provides a secondary check +but is not a substitute. + +**L3: Killswitch reliability.** The killswitch's effectiveness depends on the operating +system's file deletion semantics. On HDDs without secure erase, file overwriting may not +prevent forensic recovery. On SSDs with wear leveling, even overwriting does not guarantee +physical deletion. The deep forensic scrub does multiple passes, but this is not +equivalent to verified physical destruction. For critical-threat deployments, physical +destruction of storage media is more reliable than software scrub. + +**L4: Anchor frequency is an operational decision.** The chain's tamper-evidence +properties against insider threats depend on how frequently external anchors are taken. +FieldWitness does not enforce or automate anchor frequency. An organization that anchors +infrequently has a larger window during which insider tampering is undetectable. + +**L5: Gossip topology is not hidden.** The list of peers a node gossips with is visible +to a network observer. For organizations where federation topology is itself sensitive +information, all federation should be routed through Tor or equivalent. + +**L6: No audit of Tier 3 relay behavior.** FieldWitness does not currently provide a way for +operators to verify that the Tier 3 relay has faithfully forwarded all bundles it received. +A malicious or compromised relay could suppress specific records. The design mitigation is +to use the relay only for transport, never as an authoritative source -- but no +verification mechanism is implemented. + +**L7: Drop box source anonymity is limited.** The drop box does not log source IP addresses +in attestation records or require accounts, but it does not anonymize the source's network +connection. A source's IP is visible to the Tier 2 server operator in web server access +logs. Organizations providing source protection should use Tor for source access and may +wish to configure the web server to not log IP addresses. + +**L8: Steganalysis resistance is not guaranteed.** The steganography backend includes a +steganalysis module (`stego/steganalysis.py`) for estimating detection resistance, but +stego channels are not guaranteed to be undetectable by modern ML-based steganalysis tools +under all conditions. Stego should be treated as a covert channel with meaningful +detection risk, not a guaranteed-invisible channel. + +**L9: No formal security proof.** The security of FieldWitness's federation protocol, chain +construction, and selective disclosure has not been formally analyzed. The design draws on +established primitives (Ed25519, SHA-256, RFC 3161, Merkle trees) and patterns (gossip, +append-only logs, Certificate Transparency-inspired consistency proofs), but informal +design analysis is not a substitute for a formal proof or an independent security audit. diff --git a/docs/source-dropbox.md b/docs/source-dropbox.md index 65fc033..d1ae207 100644 --- a/docs/source-dropbox.md +++ b/docs/source-dropbox.md @@ -1,15 +1,15 @@ # Source Drop Box Setup Guide -**Audience**: Administrators setting up SooSeF's anonymous source intake feature. +**Audience**: Administrators setting up FieldWitness's anonymous source intake feature. -**Prerequisites**: A running SooSeF instance with web UI enabled (`soosef[web]` extra), +**Prerequisites**: A running FieldWitness instance with web UI enabled (`fieldwitness[web]` extra), an admin account, and HTTPS configured (self-signed is acceptable). --- ## Overview -The source drop box is a SecureDrop-style anonymous file intake built into the SooSeF web +The source drop box is a SecureDrop-style anonymous file intake built into the FieldWitness web UI. Admins create time-limited upload tokens, sources open the token URL in a browser and submit files without creating an account. Files are processed through the extract-then-strip EXIF pipeline and automatically attested on receipt. Sources receive HMAC-derived receipt @@ -24,7 +24,7 @@ codes that prove delivery. ## How It Works ``` -Admin Source SooSeF Server +Admin Source FieldWitness Server | | | |-- Create token ------------->| | | (label, expiry, max_files) | | @@ -58,11 +58,11 @@ The drop box should always be served over HTTPS. Sources must be able to trust t connection is not being intercepted. ```bash -$ soosef serve --host 0.0.0.0 +$ fieldwitness serve --host 0.0.0.0 ``` -SooSeF auto-generates a self-signed certificate on first HTTPS start. For production use, -place a reverse proxy with a proper TLS certificate in front of SooSeF. +FieldWitness auto-generates a self-signed certificate on first HTTPS start. For production use, +place a reverse proxy with a proper TLS certificate in front of FieldWitness. ### Step 2: Create an upload token @@ -95,7 +95,7 @@ Share the upload URL over an already-secure channel: ### Step 4: Source uploads files -The source opens the URL in their browser. The upload page is minimal -- no SooSeF branding, +The source opens the URL in their browser. The upload page is minimal -- no FieldWitness branding, no identifying marks, generic styling. The page works over Tor Browser with JavaScript enabled (no external resources, no CDN, no fonts, no analytics). @@ -118,7 +118,7 @@ The admin panel at `/dropbox/admin` shows: ## The Extract-Then-Strip Pipeline -Every file uploaded through the drop box is processed through SooSeF's EXIF pipeline: +Every file uploaded through the drop box is processed through FieldWitness's EXIF pipeline: 1. **Extract**: all EXIF metadata is read from the original image bytes 2. **Classify**: fields are split into evidentiary (GPS coordinates, capture timestamp -- @@ -173,10 +173,10 @@ comparing their locally computed hash with the server's receipt. | What | Where | |---|---| -| Uploaded files (stripped) | `~/.soosef/temp/dropbox/` (mode 0700) | -| Token metadata | `~/.soosef/auth/dropbox.db` (SQLite) | -| Receipt codes | `~/.soosef/auth/dropbox.db` (SQLite) | -| Attestation records | `~/.soosef/attestations/` (standard attestation log) | +| Uploaded files (stripped) | `~/.fwmetadata/temp/dropbox/` (mode 0700) | +| Token metadata | `~/.fwmetadata/auth/dropbox.db` (SQLite) | +| Receipt codes | `~/.fwmetadata/auth/dropbox.db` (SQLite) | +| Attestation records | `~/.fwmetadata/attestations/` (standard attestation log) | Expired tokens are cleaned up automatically on every admin page load. @@ -186,9 +186,9 @@ Expired tokens are cleaned up automatically on every admin page load. ### Source safety -- **No SooSeF branding** on the upload page. Generic "Secure File Upload" title. +- **No FieldWitness branding** on the upload page. Generic "Secure File Upload" title. - **No authentication required** -- sources never create accounts or reveal identity. -- **No IP logging** -- SooSeF does not log source IP addresses. Ensure your reverse proxy +- **No IP logging** -- FieldWitness does not log source IP addresses. Ensure your reverse proxy (if any) also does not log access requests to `/dropbox/upload/` paths. - **Self-contained page** -- inline CSS and JavaScript only. No external resources, CDN calls, web fonts, or analytics. Works with Tor Browser. @@ -203,12 +203,12 @@ Expired tokens are cleaned up automatically on every admin page load. Once reached, the link stops accepting uploads. - **Revoke immediately** -- if a token is compromised or no longer needed, revoke it from the admin panel. This deletes the token and all associated receipt records from SQLite. -- **Audit trail** -- token creation events are logged to `~/.soosef/audit.jsonl` with the +- **Audit trail** -- token creation events are logged to `~/.fwmetadata/audit.jsonl` with the action `dropbox.token_created`. ### Running as a Tor hidden service -For maximum source protection, run SooSeF as a Tor hidden service: +For maximum source protection, run FieldWitness as a Tor hidden service: 1. Install Tor on the server 2. Configure a hidden service in `torrc` pointing to `127.0.0.1:5000` diff --git a/docs/training/admin-operations-guide.md b/docs/training/admin-operations-guide.md index 8064a7b..3319796 100644 --- a/docs/training/admin-operations-guide.md +++ b/docs/training/admin-operations-guide.md @@ -1,7 +1,7 @@ -# SooSeF Admin Operations Guide +# FieldWitness Admin Operations Guide **Audience**: IT administrators, system operators, and technically competent journalists -responsible for deploying, configuring, and maintaining SooSeF instances for their +responsible for deploying, configuring, and maintaining FieldWitness instances for their organization. **Prerequisites**: Familiarity with Linux command line, Docker basics, and SSH. For Tier 1 @@ -15,9 +15,9 @@ This guide covers the operational tasks an admin performs after initial deployme installation and deployment, see [deployment.md](../deployment.md). For architecture details, see [docs/architecture/](../architecture/). -Your responsibilities as a SooSeF admin: +Your responsibilities as a FieldWitness admin: -1. Deploy and maintain SooSeF instances (Tier 1 USB, Tier 2 server, Tier 3 relay) +1. Deploy and maintain FieldWitness instances (Tier 1 USB, Tier 2 server, Tier 3 relay) 2. Manage user accounts and access 3. Configure threat level presets for your environment 4. Manage the source drop box @@ -42,7 +42,7 @@ Each user has: From the admin panel, issue a temporary password for a locked-out user. The user should change it on next login. All password resets are recorded in the audit log -(`~/.soosef/audit.jsonl`). +(`~/.fwmetadata/audit.jsonl`). ### Account Lockout @@ -54,7 +54,7 @@ For persistent lockout (e.g., a compromised account), delete the user from the a ### Audit Trail -All admin actions are logged to `~/.soosef/audit.jsonl` in JSON-lines format: +All admin actions are logged to `~/.fwmetadata/audit.jsonl` in JSON-lines format: ```json {"timestamp": "2026-04-01T12:00:00+00:00", "actor": "admin", "action": "user.create", "target": "user:reporter1", "outcome": "success", "source": "web"} @@ -70,13 +70,13 @@ Actions logged: `user.create`, `user.delete`, `user.password_reset`, ## 2. Threat Level Configuration -SooSeF ships four presets at `deploy/config-presets/`. Select based on your operational +FieldWitness ships four presets at `deploy/config-presets/`. Select based on your operational environment. ### Applying a Preset ```bash -$ cp deploy/config-presets/high-threat.json ~/.soosef/config.json +$ cp deploy/config-presets/high-threat.json ~/.fwmetadata/config.json ``` Restart the server to apply. @@ -92,7 +92,7 @@ Restart the server to apply. ### Custom Configuration -Edit `~/.soosef/config.json` directly. All fields have defaults. Key fields for security: +Edit `~/.fwmetadata/config.json` directly. All fields have defaults. Key fields for security: | Field | What It Controls | |---|---| @@ -104,7 +104,7 @@ Edit `~/.soosef/config.json` directly. All fields have defaults. Key fields for | `deadman_grace_hours` | Grace period after missed check-in before auto-purge | | `deadman_warning_webhook` | URL to POST a JSON warning during grace period | | `cover_name` | CN for the self-signed TLS certificate (cover/duress mode) | -| `backup_reminder_days` | Days before `soosef status` warns about overdue backups | +| `backup_reminder_days` | Days before `fieldwitness status` warns about overdue backups | > **Warning**: Setting `auth_enabled: false` disables all login requirements. Never > do this on a network-accessible instance. @@ -136,14 +136,14 @@ Share the URL over an already-secure channel only: ### What Happens When a Source Uploads -1. The source opens the URL in any browser (no account needed, no SooSeF branding) +1. The source opens the URL in any browser (no account needed, no FieldWitness branding) 2. Their browser computes SHA-256 hashes client-side before upload (SubtleCrypto) 3. Files are uploaded and processed: - EXIF metadata is extracted (evidentiary fields: GPS, timestamp) - All metadata is stripped from the stored copy (protects source device info) - The original bytes are attested (signed) before stripping 4. The source receives a receipt code (HMAC of file hash + token) -5. Files are stored in `~/.soosef/temp/dropbox/` with mode 0700 +5. Files are stored in `~/.fwmetadata/temp/dropbox/` with mode 0700 ### Revoking Tokens @@ -157,16 +157,16 @@ their receipt code. This returns the filename, SHA-256, and reception timestamp. ### Operational Security -- The upload page has no SooSeF branding -- it is a minimal HTML form +- The upload page has no FieldWitness branding -- it is a minimal HTML form - No external resources are loaded (no CDN, fonts, analytics) -- Tor Browser compatible -- SooSeF does not log source IP addresses +- FieldWitness does not log source IP addresses - If using a reverse proxy (nginx, Caddy), disable access logging for `/dropbox/upload/` - Tokens auto-expire and are cleaned up on every admin page load -- For maximum source protection, run SooSeF as a Tor hidden service +- For maximum source protection, run FieldWitness as a Tor hidden service ### Storage Management -Uploaded files accumulate in `~/.soosef/temp/dropbox/`. Periodically review and process +Uploaded files accumulate in `~/.fwmetadata/temp/dropbox/`. Periodically review and process submissions, then remove them from the temp directory. The files are not automatically cleaned up (they persist until you act on them or the killswitch fires). @@ -176,34 +176,34 @@ cleaned up (they persist until you act on them or the killswitch fires). ### Two Key Domains -SooSeF manages two independent key types: +FieldWitness manages two independent key types: | Key | Algorithm | Location | Purpose | |---|---|---|---| -| **Identity key** | Ed25519 | `~/.soosef/identity/` | Sign attestations, chain records | -| **Channel key** | AES-256-GCM (Argon2id-derived) | `~/.soosef/stegasoo/channel.key` | Steganographic encoding | +| **Identity key** | Ed25519 | `~/.fwmetadata/identity/` | Sign attestations, chain records | +| **Channel key** | AES-256-GCM (Argon2id-derived) | `~/.fwmetadata/stego/channel.key` | Steganographic encoding | These are never merged. Rotating one does not affect the other. ### Key Rotation **Identity rotation** archives the old keypair and generates a new one. If the chain is -enabled, a `soosef/key-rotation-v1` record is signed by the OLD key, creating a +enabled, a `fieldwitness/key-rotation-v1` record is signed by the OLD key, creating a verifiable trust chain. ```bash -$ soosef keys rotate-identity +$ fieldwitness keys rotate-identity ``` After rotating, immediately: -1. Take a fresh backup (`soosef keys export`) +1. Take a fresh backup (`fieldwitness keys export`) 2. Notify all collaborators of the new fingerprint 3. Update trusted-key lists at partner organizations **Channel rotation** archives the old key and generates a new one: ```bash -$ soosef keys rotate-channel +$ fieldwitness keys rotate-channel ``` After rotating, share the new channel key with all stego correspondents. @@ -214,7 +214,7 @@ Import collaborator public keys so you can verify their attestations and accept federation bundles: ```bash -$ soosef keys trust --import /media/usb/partner-pubkey.pem +$ fieldwitness keys trust --import /media/usb/partner-pubkey.pem ``` Always verify fingerprints out-of-band (in person or over a known-secure voice channel). @@ -222,25 +222,25 @@ Always verify fingerprints out-of-band (in person or over a known-secure voice c List trusted keys: ```bash -$ soosef keys show +$ fieldwitness keys show ``` Remove a trusted key: ```bash -$ soosef keys untrust +$ fieldwitness keys untrust ``` ### Backup Schedule -SooSeF warns when backups are overdue (configurable via `backup_reminder_days`). +FieldWitness warns when backups are overdue (configurable via `backup_reminder_days`). ```bash # Create encrypted backup -$ soosef keys export -o /media/usb/backup.enc +$ fieldwitness keys export -o /media/usb/backup.enc # Check backup status -$ soosef status +$ fieldwitness status ``` Store backups on separate physical media, in a different location from the device. @@ -249,7 +249,7 @@ Store backups on separate physical media, in a different location from the devic ## 5. Federation Setup -Federation allows multiple SooSeF instances to exchange attestation records. +Federation allows multiple FieldWitness instances to exchange attestation records. ### Adding Federation Peers @@ -266,19 +266,19 @@ and Ed25519 fingerprint. Before two organizations can federate, exchange public keys: -1. Export your public key: `cp ~/.soosef/identity/public.pem /media/usb/our-pubkey.pem` +1. Export your public key: `cp ~/.fwmetadata/identity/public.pem /media/usb/our-pubkey.pem` 2. Give it to the partner organization (physical handoff or secure channel) -3. Import their key: `soosef keys trust --import /media/usb/their-pubkey.pem` +3. Import their key: `fieldwitness keys trust --import /media/usb/their-pubkey.pem` 4. Verify fingerprints out-of-band ### Exporting Attestation Bundles ```bash # Export all records -$ soosef chain export --output /media/usb/bundle.zip +$ fieldwitness chain export --output /media/usb/bundle.zip # Export a specific range -$ soosef chain export --start 100 --end 200 --output /media/usb/bundle.zip +$ fieldwitness chain export --start 100 --end 200 --output /media/usb/bundle.zip # Export filtered by investigation # (investigation tag is set during attestation) @@ -296,7 +296,7 @@ On the receiving instance, imported records are: If the Tier 2 server and Tier 3 relay have network connectivity, gossip sync runs automatically at the configured interval (default: 60 seconds, set via -`VERISOO_GOSSIP_INTERVAL` environment variable). +`FIELDWITNESS_GOSSIP_INTERVAL` environment variable). Gossip flow: 1. Nodes exchange Merkle roots @@ -326,7 +326,7 @@ No network connectivity is required at any point. Verify the full chain periodically: ```bash -$ soosef chain verify +$ fieldwitness chain verify ``` This checks all hash linkage and Ed25519 signatures. It also verifies key rotation @@ -338,10 +338,10 @@ Anchor the chain head to prove it existed before a given time: ```bash # Automated (requires network) -$ soosef chain anchor --tsa https://freetsa.org/tsr +$ fieldwitness chain anchor --tsa https://freetsa.org/tsr # Manual (prints hash for external submission) -$ soosef chain anchor +$ fieldwitness chain anchor ``` A single anchor implicitly timestamps every prior record (the chain is append-only). @@ -358,7 +358,7 @@ For legal discovery or court orders, produce a proof showing specific records wh keeping others redacted: ```bash -$ soosef chain disclose -i 42,43,44 -o disclosure.json +$ fieldwitness chain disclose -i 42,43,44 -o disclosure.json ``` The output includes full records for selected indices and hash-only entries for everything @@ -370,7 +370,7 @@ else. A third party can verify the selected records are part of an unbroken chai ### Evidence Packages -For handing evidence to lawyers, courts, or organizations without SooSeF: +For handing evidence to lawyers, courts, or organizations without FieldWitness: Self-contained ZIP containing original images, attestation records, chain data, your public key, a standalone `verify.py`, and a README. The recipient verifies with: @@ -413,7 +413,7 @@ Returns capabilities (stego-lsb, stego-dct, attest, fieldkit, chain). ### System Status ```bash -$ soosef status --json +$ fieldwitness status --json ``` Checks: identity key, channel key, chain integrity, dead man's switch state, backup @@ -440,7 +440,7 @@ The Docker images include `HEALTHCHECK` directives that poll `/health` every 30 ### Device Seizure (Imminent) -1. Trigger killswitch: `soosef fieldkit purge --confirm CONFIRM-PURGE` +1. Trigger killswitch: `fieldwitness fieldkit purge --confirm CONFIRM-PURGE` 2. For Tier 1 USB: pull the USB stick and destroy it physically if possible 3. Verify with a separate device that federation copies are intact @@ -465,8 +465,8 @@ The Docker images include `HEALTHCHECK` directives that poll `/health` every 30 Data is gone. Restore from the most recent backup: ```bash -$ soosef init -$ soosef keys import -b /media/usb/backup.enc +$ fieldwitness init +$ fieldwitness keys import -b /media/usb/backup.enc ``` Federation copies of attestation data are unaffected. Local attestations created since @@ -480,22 +480,22 @@ the last federation sync or backup are lost. | Task | Frequency | Command | |---|---|---| -| Check system status | Daily | `soosef status` | -| Check in (if deadman armed) | Per interval | `soosef fieldkit checkin` | -| Backup keys | Per `backup_reminder_days` | `soosef keys export` | -| Verify chain integrity | Weekly | `soosef chain verify` | -| Anchor chain | Weekly | `soosef chain anchor` | +| Check system status | Daily | `fieldwitness status` | +| Check in (if deadman armed) | Per interval | `fieldwitness fieldkit checkin` | +| Backup keys | Per `backup_reminder_days` | `fieldwitness keys export` | +| Verify chain integrity | Weekly | `fieldwitness chain verify` | +| Anchor chain | Weekly | `fieldwitness chain anchor` | | Review drop box submissions | As needed | `/dropbox/admin` | -| Clean temp files | Monthly | Remove processed files from `~/.soosef/temp/` | +| Clean temp files | Monthly | Remove processed files from `~/.fwmetadata/temp/` | | Create cold archive | Monthly | Export via CLI or web | -| Update SooSeF | As releases are available | `pip install --upgrade soosef` | +| Update FieldWitness | As releases are available | `pip install --upgrade fieldwitness` | ### Docker Volume Backup ```bash $ docker compose stop server $ docker run --rm -v server-data:/data -v /backup:/backup \ - busybox tar czf /backup/soosef-$(date +%Y%m%d).tar.gz -C /data . + busybox tar czf /backup/fieldwitness-$(date +%Y%m%d).tar.gz -C /data . $ docker compose start server ``` @@ -505,8 +505,8 @@ $ docker compose start server periodically. The audit log is append-only; truncate by copying the tail: ```bash -$ tail -n 10000 ~/.soosef/audit.jsonl > ~/.soosef/audit.jsonl.tmp -$ mv ~/.soosef/audit.jsonl.tmp ~/.soosef/audit.jsonl +$ tail -n 10000 ~/.fwmetadata/audit.jsonl > ~/.fwmetadata/audit.jsonl.tmp +$ mv ~/.fwmetadata/audit.jsonl.tmp ~/.fwmetadata/audit.jsonl ``` > **Warning**: Truncating the audit log removes historical records. Archive the full diff --git a/docs/training/admin-reference.md b/docs/training/admin-reference.md index d59a04d..76aa0aa 100644 --- a/docs/training/admin-reference.md +++ b/docs/training/admin-reference.md @@ -1,7 +1,7 @@ # Administrator Quick Reference **Audience**: IT staff and technical leads responsible for deploying and maintaining -SooSeF instances. +FieldWitness instances. --- @@ -47,23 +47,23 @@ Exposes port 8001 (federation API only). ### Kubernetes ```bash -$ docker build -t soosef-server --target server -f deploy/docker/Dockerfile . -$ docker build -t soosef-relay --target relay -f deploy/docker/Dockerfile . +$ docker build -t fieldwitness-server --target server -f deploy/docker/Dockerfile . +$ docker build -t fieldwitness-relay --target relay -f deploy/docker/Dockerfile . $ kubectl apply -f deploy/kubernetes/namespace.yaml $ kubectl apply -f deploy/kubernetes/server-deployment.yaml $ kubectl apply -f deploy/kubernetes/relay-deployment.yaml ``` -Single-replica only. SooSeF uses SQLite -- do not scale horizontally. +Single-replica only. FieldWitness uses SQLite -- do not scale horizontally. --- ## Threat Level Presets -Copy the appropriate preset to configure SooSeF for the operational environment: +Copy the appropriate preset to configure FieldWitness for the operational environment: ```bash -$ cp deploy/config-presets/-threat.json ~/.soosef/config.json +$ cp deploy/config-presets/-threat.json ~/.fwmetadata/config.json ``` | Level | Session | Killswitch | Dead Man | Cover Name | @@ -81,45 +81,45 @@ $ cp deploy/config-presets/-threat.json ~/.soosef/config.json | Command | Description | |---|---| -| `soosef init` | Create directory structure, generate keys, write default config | -| `soosef serve --host 0.0.0.0` | Start web UI (LAN-accessible) | -| `soosef status` | Pre-flight check: keys, chain, deadman, backup, geofence | -| `soosef status --json` | Machine-readable status output | +| `fieldwitness init` | Create directory structure, generate keys, write default config | +| `fieldwitness serve --host 0.0.0.0` | Start web UI (LAN-accessible) | +| `fieldwitness status` | Pre-flight check: keys, chain, deadman, backup, geofence | +| `fieldwitness status --json` | Machine-readable status output | ### Keys | Command | Description | |---|---| -| `soosef keys show` | Display current key info and fingerprints | -| `soosef keys export -o backup.enc` | Export encrypted key bundle | -| `soosef keys import -b backup.enc` | Import key bundle from backup | -| `soosef keys rotate-identity` | Rotate Ed25519 identity (records in chain) | -| `soosef keys rotate-channel` | Rotate AES-256-GCM channel key | -| `soosef keys trust --import pubkey.pem` | Trust a collaborator's public key | +| `fieldwitness keys show` | Display current key info and fingerprints | +| `fieldwitness keys export -o backup.enc` | Export encrypted key bundle | +| `fieldwitness keys import -b backup.enc` | Import key bundle from backup | +| `fieldwitness keys rotate-identity` | Rotate Ed25519 identity (records in chain) | +| `fieldwitness keys rotate-channel` | Rotate AES-256-GCM channel key | +| `fieldwitness keys trust --import pubkey.pem` | Trust a collaborator's public key | ### Fieldkit | Command | Description | |---|---| -| `soosef fieldkit status` | Show fieldkit state (deadman, geofence, USB, tamper) | -| `soosef fieldkit checkin` | Reset dead man's switch timer | -| `soosef fieldkit check-deadman` | Check if deadman timer expired (for cron) | -| `soosef fieldkit purge --confirm CONFIRM-PURGE` | Activate killswitch | -| `soosef fieldkit geofence set --lat X --lon Y --radius M` | Set GPS boundary | -| `soosef fieldkit usb snapshot` | Record USB whitelist baseline | -| `soosef fieldkit tamper baseline` | Record file integrity baseline | +| `fieldwitness fieldkit status` | Show fieldkit state (deadman, geofence, USB, tamper) | +| `fieldwitness fieldkit checkin` | Reset dead man's switch timer | +| `fieldwitness fieldkit check-deadman` | Check if deadman timer expired (for cron) | +| `fieldwitness fieldkit purge --confirm CONFIRM-PURGE` | Activate killswitch | +| `fieldwitness fieldkit geofence set --lat X --lon Y --radius M` | Set GPS boundary | +| `fieldwitness fieldkit usb snapshot` | Record USB whitelist baseline | +| `fieldwitness fieldkit tamper baseline` | Record file integrity baseline | ### Chain and Evidence | Command | Description | |---|---| -| `soosef chain status` | Show chain head, length, integrity | -| `soosef chain verify` | Verify full chain (hashes + signatures) | -| `soosef chain log --count 20` | Show recent chain entries | -| `soosef chain export -o bundle.zip` | Export attestation bundle | -| `soosef chain disclose -i 5,12,47 -o disclosure.json` | Selective disclosure | -| `soosef chain anchor` | Manual anchor (prints hash for external witness) | -| `soosef chain anchor --tsa https://freetsa.org/tsr` | RFC 3161 automated anchor | +| `fieldwitness chain status` | Show chain head, length, integrity | +| `fieldwitness chain verify` | Verify full chain (hashes + signatures) | +| `fieldwitness chain log --count 20` | Show recent chain entries | +| `fieldwitness chain export -o bundle.zip` | Export attestation bundle | +| `fieldwitness chain disclose -i 5,12,47 -o disclosure.json` | Selective disclosure | +| `fieldwitness chain anchor` | Manual anchor (prints hash for external witness) | +| `fieldwitness chain anchor --tsa https://freetsa.org/tsr` | RFC 3161 automated anchor | --- @@ -132,7 +132,7 @@ The web UI admin panel at `/admin` provides: - Reset passwords (temporary password issued) - View active sessions -User credentials are stored in SQLite at `~/.soosef/auth/soosef.db`. +User credentials are stored in SQLite at `~/.fwmetadata/auth/fieldwitness.db`. --- @@ -140,9 +140,9 @@ User credentials are stored in SQLite at `~/.soosef/auth/soosef.db`. | What | How often | Command | |---|---|---| -| Key bundle | After every rotation, weekly minimum | `soosef keys export -o backup.enc` | -| Cold archive | Weekly or before travel | `soosef archive export --include-keys -o archive.zip` | -| Docker volume | Before updates | `docker compose stop server && docker run --rm -v server-data:/data -v /backup:/backup busybox tar czf /backup/soosef-$(date +%Y%m%d).tar.gz -C /data .` | +| Key bundle | After every rotation, weekly minimum | `fieldwitness keys export -o backup.enc` | +| Cold archive | Weekly or before travel | `fieldwitness archive export --include-keys -o archive.zip` | +| Docker volume | Before updates | `docker compose stop server && docker run --rm -v server-data:/data -v /backup:/backup busybox tar czf /backup/fieldwitness-$(date +%Y%m%d).tar.gz -C /data .` | Store backups on separate physical media. Keep one copy offsite. @@ -151,11 +151,11 @@ Store backups on separate physical media. Keep one copy offsite. ## Federation Setup 1. Exchange public keys between organizations (verify fingerprints out-of-band) -2. Import collaborator keys: `soosef keys trust --import /path/to/pubkey.pem` +2. Import collaborator keys: `fieldwitness keys trust --import /path/to/pubkey.pem` 3. Register peers via web UI at `/federation` or via CLI 4. Gossip starts automatically; monitor at `/federation` -For airgapped federation: `soosef chain export` to USB, carry to partner, import there. +For airgapped federation: `fieldwitness chain export` to USB, carry to partner, import there. --- @@ -177,7 +177,7 @@ For airgapped federation: `soosef chain export` to USB, carry to partner, import - [ ] Disable unnecessary services (bluetooth, avahi-daemon) - [ ] Apply a threat level preset appropriate for the environment - [ ] Set `cover_name` in config if operating under cover -- [ ] Set `SOOSEF_DATA_DIR` to an inconspicuous path if needed +- [ ] Set `FIELDWITNESS_DATA_DIR` to an inconspicuous path if needed - [ ] Enable HTTPS (default) or place behind a reverse proxy with TLS - [ ] Create systemd service for bare metal (see `docs/deployment.md` Section 7) - [ ] Set up regular backups (key bundle + cold archive) @@ -194,11 +194,11 @@ For airgapped federation: `soosef chain export` to USB, carry to partner, import | Web UI unreachable from LAN | `host` must be `0.0.0.0`, not `127.0.0.1`. Check firewall. | | Docker container exits | `docker compose logs server` -- check for port conflict or volume permissions | | Dead man fires unexpectedly | Service crashed and exceeded interval+grace. Ensure `Restart=on-failure`. | -| Permission errors on `~/.soosef/` | Run SooSeF as the same user who ran `soosef init` | +| Permission errors on `~/.fwmetadata/` | Run FieldWitness as the same user who ran `fieldwitness init` | | Drop box tokens expire immediately | System clock wrong. Run `date -u` and fix if needed. | | Chain anchor TSA fails | Requires network. Use manual anchor on airgapped devices. | | Account locked out | Wait for lockout to expire, or restart the server. | -| SSL cert shows wrong name | Delete `~/.soosef/certs/cert.pem`, set `cover_name`, restart. | +| SSL cert shows wrong name | Delete `~/.fwmetadata/certs/cert.pem`, set `cover_name`, restart. | --- @@ -215,5 +215,5 @@ $ curl http://localhost:8000/health $ curl http://localhost:8001/health # Full system status -$ soosef status --json +$ fieldwitness status --json ``` diff --git a/docs/training/emergency-card.md b/docs/training/emergency-card.md index 9bdd014..82556f6 100644 --- a/docs/training/emergency-card.md +++ b/docs/training/emergency-card.md @@ -1,6 +1,6 @@ # Emergency Reference Card -**Audience**: All SooSeF users. Print, laminate, and carry in your wallet. +**Audience**: All FieldWitness users. Print, laminate, and carry in your wallet. --- @@ -17,7 +17,7 @@ In the browser: **Fieldkit** > **Emergency Purge** > type `CONFIRM-PURGE` > clic From a terminal: ``` -soosef fieldkit purge --confirm CONFIRM-PURGE +fieldwitness fieldkit purge --confirm CONFIRM-PURGE ``` ### Option 3: Hardware button (Raspberry Pi only) @@ -51,7 +51,7 @@ If enabled, you must check in before the deadline or all data will be destroyed. **Check in**: Browser > **Fieldkit** > **Check In** -Or: `soosef fieldkit checkin` +Or: `fieldwitness fieldkit checkin` If you cannot check in, contact your editor. They may be able to disarm it remotely. diff --git a/docs/training/reporter-field-guide.md b/docs/training/reporter-field-guide.md index 1c9398e..6024e50 100644 --- a/docs/training/reporter-field-guide.md +++ b/docs/training/reporter-field-guide.md @@ -1,22 +1,22 @@ -# SooSeF Reporter Field Guide +# FieldWitness Reporter Field Guide -**Audience**: Reporters, field researchers, and documentarians using SooSeF to protect +**Audience**: Reporters, field researchers, and documentarians using FieldWitness to protect and verify their work. No technical background required. -**Prerequisites**: A working SooSeF instance (Tier 1 USB or web UI access to a Tier 2 +**Prerequisites**: A working FieldWitness instance (Tier 1 USB or web UI access to a Tier 2 server). Your IT admin should have set this up for you. --- -## What SooSeF Does For You +## What FieldWitness Does For You -SooSeF helps you do three things: +FieldWitness helps you do three things: 1. **Prove your photos and files are authentic** -- every photo you attest gets a cryptographic signature that proves you took it, when, and that it has not been tampered with since. 2. **Hide messages in images** -- send encrypted messages that look like ordinary photos. -3. **Destroy everything if compromised** -- if your device is about to be seized, SooSeF +3. **Destroy everything if compromised** -- if your device is about to be seized, FieldWitness can erase all evidence of itself and your data in seconds. --- @@ -37,7 +37,7 @@ permanent, tamper-evident record. 5. Add a location if relevant (optional) 6. Click **Attest** -SooSeF will: +FieldWitness will: - Extract GPS coordinates and timestamp from the photo's EXIF data (for the provenance record) - Strip device-identifying information (serial numbers, firmware version) from the stored copy - Sign the photo with your Ed25519 identity key @@ -46,7 +46,7 @@ SooSeF will: **Through the CLI (if available):** ```bash -$ soosef attest IMAGE photo.jpg --caption "Market protest, central square" +$ fieldwitness attest IMAGE photo.jpg --caption "Market protest, central square" ``` > **Warning**: Attest the original, unedited photo. If you crop, filter, or resize @@ -57,12 +57,12 @@ $ soosef attest IMAGE photo.jpg --caption "Market protest, central square" If you have a folder of photos from a field visit: ```bash -$ soosef attest batch ./field-photos/ --caption "Site visit 2026-04-01" +$ fieldwitness attest batch ./field-photos/ --caption "Site visit 2026-04-01" ``` ### Checking Your Status -Run `soosef status` or visit the web UI home page to see: +Run `fieldwitness status` or visit the web UI home page to see: - Whether your identity key is set up - How many attestations you have - Whether your dead man's switch needs a check-in @@ -92,7 +92,7 @@ message, passphrase, and PIN. **CLI:** ```bash -$ soosef stego encode vacation.jpg -r shared_photo.jpg -m "Meeting moved to Thursday" +$ fieldwitness stego encode vacation.jpg -r shared_photo.jpg -m "Meeting moved to Thursday" # Passphrase: (enter your passphrase, hidden) # PIN: (enter your PIN, hidden) ``` @@ -101,22 +101,22 @@ The output is a normal-looking image file that contains your hidden message. ### Transport-Aware Encoding -If you are sending the image through a messaging app, tell SooSeF which platform. The -app will recompress images, so SooSeF needs to use a survival-resistant encoding: +If you are sending the image through a messaging app, tell FieldWitness which platform. The +app will recompress images, so FieldWitness needs to use a survival-resistant encoding: ```bash -$ soosef stego encode photo.jpg -r shared.jpg -m "Safe house confirmed" --transport whatsapp -$ soosef stego encode photo.jpg -r shared.jpg -m "Safe house confirmed" --transport signal -$ soosef stego encode photo.jpg -r shared.jpg -m "Safe house confirmed" --transport telegram +$ fieldwitness stego encode photo.jpg -r shared.jpg -m "Safe house confirmed" --transport whatsapp +$ fieldwitness stego encode photo.jpg -r shared.jpg -m "Safe house confirmed" --transport signal +$ fieldwitness stego encode photo.jpg -r shared.jpg -m "Safe house confirmed" --transport telegram ``` -> **Warning**: Never reuse the same carrier image twice. SooSeF will warn you if you +> **Warning**: Never reuse the same carrier image twice. FieldWitness will warn you if you > do. Comparing two versions of the same image trivially reveals steganographic changes. ### Decoding a Message ```bash -$ soosef stego decode received_image.jpg -r shared_photo.jpg +$ fieldwitness stego decode received_image.jpg -r shared_photo.jpg # Passphrase: (same passphrase) # PIN: (same PIN) ``` @@ -126,7 +126,7 @@ $ soosef stego decode received_image.jpg -r shared_photo.jpg ## Check-In (Dead Man's Switch) If your admin has enabled the dead man's switch, you must check in regularly. If you miss -your check-in window, SooSeF assumes something has gone wrong and will eventually destroy +your check-in window, FieldWitness assumes something has gone wrong and will eventually destroy all data to protect you. **Check in through the web UI:** Visit the **Fieldkit** page and click **Check In**. @@ -134,7 +134,7 @@ all data to protect you. **Check in through the CLI:** ```bash -$ soosef fieldkit checkin +$ fieldwitness fieldkit checkin ``` > **Warning**: If you will be unable to check in (traveling without the device, planned @@ -150,7 +150,7 @@ If your device is about to be seized or compromised: **CLI:** ```bash -$ soosef fieldkit purge --confirm CONFIRM-PURGE +$ fieldwitness fieldkit purge --confirm CONFIRM-PURGE ``` **Web UI:** Visit the **Fieldkit** page and use the emergency purge button. @@ -169,9 +169,9 @@ access. 4. All attestation records and chain data 5. Temporary files and audit logs 6. Configuration -7. System log entries mentioning SooSeF -8. Python bytecache and pip metadata (to hide that SooSeF was installed) -9. The SooSeF package itself +7. System log entries mentioning FieldWitness +8. Python bytecache and pip metadata (to hide that FieldWitness was installed) +9. The FieldWitness package itself > **Warning**: This is irreversible. Make sure you have recent backups stored > separately before relying on the killswitch. See "Backups" below. @@ -180,25 +180,25 @@ access. ## Backups -Back up your keys regularly. SooSeF will remind you if your backup is overdue. +Back up your keys regularly. FieldWitness will remind you if your backup is overdue. ### Creating a Backup ```bash -$ soosef keys export -o /media/usb/soosef-backup.enc +$ fieldwitness keys export -o /media/usb/fieldwitness-backup.enc ``` You will be prompted for a passphrase. This creates an encrypted bundle containing your identity key and channel key. Store the USB drive **in a different physical location** -from your SooSeF device. +from your FieldWitness device. ### Restoring From Backup -On a fresh SooSeF instance: +On a fresh FieldWitness instance: ```bash -$ soosef init -$ soosef keys import -b /media/usb/soosef-backup.enc +$ fieldwitness init +$ fieldwitness keys import -b /media/usb/fieldwitness-backup.enc ``` --- @@ -206,11 +206,11 @@ $ soosef keys import -b /media/usb/soosef-backup.enc ## Evidence Packages When you need to hand evidence to a lawyer, a court, or a partner organization that does -not use SooSeF: +not use FieldWitness: 1. Go to the web UI or use the CLI to create an evidence package 2. Select the photos to include -3. SooSeF creates a ZIP file containing: +3. FieldWitness creates a ZIP file containing: - Your original photos - Attestation records with signatures - The chain segment proving order and integrity @@ -218,7 +218,7 @@ not use SooSeF: - A standalone verification script - A README with instructions -The recipient can verify the evidence using only Python -- they do not need SooSeF. +The recipient can verify the evidence using only Python -- they do not need FieldWitness. --- @@ -238,7 +238,7 @@ encrypted with keys derived from the passphrase, PIN, and reference photo. If yo any of the three, the message cannot be recovered. **You need to share evidence with a court**: Use selective disclosure -(`soosef chain disclose`) to produce a proof that includes only the specific records +(`fieldwitness chain disclose`) to produce a proof that includes only the specific records requested. The court can verify these records are part of an authentic, unbroken chain without seeing your other work. @@ -259,5 +259,5 @@ the killswitch fires automatically. - **Back up regularly** and store backups in a separate physical location. - **Lock the browser** or close it when you walk away. Session timeouts help, but do not rely on them. -- **Do not discuss SooSeF by name** in environments where your communications may be +- **Do not discuss FieldWitness by name** in environments where your communications may be monitored. If `cover_name` is configured, the tool presents itself under that name. diff --git a/docs/training/reporter-quickstart.md b/docs/training/reporter-quickstart.md index 0bfab9b..3e641cb 100644 --- a/docs/training/reporter-quickstart.md +++ b/docs/training/reporter-quickstart.md @@ -1,6 +1,6 @@ # Reporter Quick-Start Card -**Audience**: Field reporters using a SooSeF Tier 1 bootable USB device. +**Audience**: Field reporters using a FieldWitness Tier 1 bootable USB device. No technical background assumed. **Print this page on a single sheet, laminate it, and keep it with the USB stick.** @@ -14,7 +14,7 @@ No technical background assumed. 3. **Enter your passphrase** when the blue screen appears (this unlocks your data) 4. **Wait for the browser** to open automatically -You are now running SooSeF. The laptop's own hard drive is never touched. +You are now running FieldWitness. The laptop's own hard drive is never touched. --- @@ -54,7 +54,7 @@ If your admin has enabled the dead man's switch, you must check in regularly. Or from a terminal: ``` -soosef fieldkit checkin +fieldwitness fieldkit checkin ``` If you miss your check-in window, the system will destroy all data after the grace period. @@ -80,7 +80,7 @@ Everything is gone. Keys, photos, attestations, messages -- all destroyed. 1. **Close the browser** 2. **Pull the USB stick** -The laptop returns to its normal state. No trace of SooSeF remains. +The laptop returns to its normal state. No trace of FieldWitness remains. --- diff --git a/frontends/web/app.py b/frontends/web/app.py index 05967d3..6895e88 100644 --- a/frontends/web/app.py +++ b/frontends/web/app.py @@ -1,27 +1,27 @@ """ -SooSeF Web Frontend +FieldWitness Web Frontend -Flask application factory that unifies Stegasoo (steganography) and Verisoo +Flask application factory that unifies Stego (steganography) and Attest (provenance attestation) into a single web UI with fieldkit security features. ARCHITECTURE ============ -The stegasoo web UI (3,600+ lines, 60 routes) is mounted wholesale via -_register_stegasoo_routes() rather than being rewritten into a blueprint. +The stego web UI (3,600+ lines, 60 routes) is mounted wholesale via +_register_stego_routes() rather than being rewritten into a blueprint. This preserves the battle-tested subprocess isolation, async job management, and all existing route logic without modification. -SooSeF-native features (attest, fieldkit, keys) are clean blueprints. +FieldWitness-native features (attest, fieldkit, keys) are clean blueprints. - Stegasoo routes (mounted at root): + Stego routes (mounted at root): /encode, /decode, /generate, /tools, /api/* - SooSeF blueprints: + FieldWitness blueprints: /attest, /verify → attest blueprint /fieldkit/* → fieldkit blueprint /keys/* → keys blueprint - /admin/* → admin blueprint (extends stegasoo's) + /admin/* → admin blueprint (extends stego's) """ import io @@ -42,18 +42,18 @@ from flask import ( url_for, ) -import soosef -from soosef.config import SoosefConfig -from soosef.paths import INSTANCE_DIR, SECRET_KEY_FILE, TEMP_DIR, ensure_dirs +import fieldwitness +from fieldwitness.config import FieldWitnessConfig +from fieldwitness.paths import INSTANCE_DIR, SECRET_KEY_FILE, TEMP_DIR, ensure_dirs # Suppress numpy/scipy warnings in subprocesses os.environ["NUMPY_MADVISE_HUGEPAGE"] = "0" os.environ["OMP_NUM_THREADS"] = "1" -def create_app(config: SoosefConfig | None = None) -> Flask: +def create_app(config: FieldWitnessConfig | None = None) -> Flask: """Application factory.""" - config = config or SoosefConfig.load() + config = config or FieldWitnessConfig.load() ensure_dirs() web_dir = Path(__file__).parent @@ -68,7 +68,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask: app.config["MAX_CONTENT_LENGTH"] = config.max_upload_mb * 1024 * 1024 app.config["AUTH_ENABLED"] = config.auth_enabled app.config["HTTPS_ENABLED"] = config.https_enabled - app.config["SOOSEF_CONFIG"] = config + app.config["FIELDWITNESS_CONFIG"] = config # Session security: timeout + secure cookie flags from datetime import timedelta @@ -84,7 +84,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask: csrf = CSRFProtect(app) - # Point temp_storage at ~/.soosef/temp/ before any routes run, so all + # Point temp_storage at ~/.fieldwitness/temp/ before any routes run, so all # uploaded files land where the killswitch's destroy_temp_files step # expects them. Must happen after ensure_dirs() so the directory exists. import temp_storage as _ts @@ -103,10 +103,10 @@ def create_app(config: SoosefConfig | None = None) -> Flask: init_auth(app) - # ── Register stegasoo routes ────────────────────────────────── - _register_stegasoo_routes(app) + # ── Register stego routes ────────────────────────────────── + _register_stego_routes(app) - # ── Register SooSeF-native blueprints ───────────────────────── + # ── Register FieldWitness-native blueprints ───────────────────────── from frontends.web.blueprints.attest import bp as attest_bp from frontends.web.blueprints.fieldkit import bp as fieldkit_bp from frontends.web.blueprints.keys import bp as keys_bp @@ -131,7 +131,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask: @app.context_processor def inject_globals(): - from soosef.keystore import KeystoreManager + from fieldwitness.keystore import KeystoreManager ks = KeystoreManager() ks_status = ks.status() @@ -139,7 +139,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask: # Fieldkit alert level fieldkit_status = "ok" if config.deadman_enabled: - from soosef.fieldkit.deadman import DeadmanSwitch + from fieldwitness.fieldkit.deadman import DeadmanSwitch dm = DeadmanSwitch() if dm.should_fire(): @@ -147,10 +147,10 @@ def create_app(config: SoosefConfig | None = None) -> Flask: elif dm.is_overdue(): fieldkit_status = "warn" - # Stegasoo capabilities + # Stego capabilities try: - from soosef.stegasoo import HAS_AUDIO_SUPPORT, get_channel_status, has_dct_support - from soosef.stegasoo.constants import ( + from fieldwitness.stego import HAS_AUDIO_SUPPORT, get_channel_status, has_dct_support + from fieldwitness.stego.constants import ( DEFAULT_PASSPHRASE_WORDS, MAX_FILE_PAYLOAD_SIZE, MAX_MESSAGE_CHARS, @@ -166,7 +166,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask: has_audio = HAS_AUDIO_SUPPORT channel_status = get_channel_status() - # Stegasoo-specific template vars (needed by stego templates) + # Stego-specific template vars (needed by stego templates) stego_vars = { "has_dct": has_dct, "has_audio": has_audio, @@ -188,13 +188,13 @@ def create_app(config: SoosefConfig | None = None) -> Flask: has_audio = False stego_vars = {} - # Verisoo availability + # Attest availability try: - import soosef.verisoo # noqa: F401 + import fieldwitness.attest # noqa: F401 - has_verisoo = True + has_attest = True except ImportError: - has_verisoo = False + has_attest = False # Saved channel keys for authenticated users saved_channel_keys = [] @@ -209,8 +209,8 @@ def create_app(config: SoosefConfig | None = None) -> Flask: pass base_vars = { - "version": soosef.__version__, - "has_verisoo": has_verisoo, + "version": fieldwitness.__version__, + "has_attest": has_attest, "has_fieldkit": config.killswitch_enabled or config.deadman_enabled, "fieldkit_status": fieldkit_status, "channel_configured": ks_status.has_channel_key, @@ -247,22 +247,22 @@ def create_app(config: SoosefConfig | None = None) -> Flask: # (deadman status, key presence, memory, etc. are operational intel) if not auth_is_authenticated(): from flask import jsonify - return jsonify({"status": "ok", "version": __import__("soosef").__version__}) + return jsonify({"status": "ok", "version": __import__("fieldwitness").__version__}) import platform import sys from flask import jsonify - from soosef.keystore.manager import KeystoreManager + from fieldwitness.keystore.manager import KeystoreManager ks = KeystoreManager() # Core modules modules = {} for name, import_path in [ - ("stegasoo", "soosef.stegasoo"), - ("verisoo", "soosef.verisoo"), + ("stego", "fieldwitness.stego"), + ("attest", "fieldwitness.attest"), ]: try: mod = __import__(import_path, fromlist=["__version__"]) @@ -275,27 +275,27 @@ def create_app(config: SoosefConfig | None = None) -> Flask: # DCT steganography try: - from soosef.stegasoo import has_dct_support + from fieldwitness.stego import has_dct_support capabilities["stego_dct"] = { "status": "ok" if has_dct_support() else "unavailable", - "hint": None if has_dct_support() else "Install soosef[stego-dct] (scipy, jpeglib, reedsolo)", + "hint": None if has_dct_support() else "Install fieldwitness[stego-dct] (scipy, jpeglib, reedsolo)", } except ImportError: - capabilities["stego_dct"] = {"status": "missing", "hint": "Install soosef[stego-dct]"} + capabilities["stego_dct"] = {"status": "missing", "hint": "Install fieldwitness[stego-dct]"} # Audio steganography try: - from soosef.stegasoo import HAS_AUDIO_SUPPORT + from fieldwitness.stego import HAS_AUDIO_SUPPORT capabilities["stego_audio"] = { "status": "ok" if HAS_AUDIO_SUPPORT else "unavailable", - "hint": None if HAS_AUDIO_SUPPORT else "Install soosef[stego-audio] (soundfile, numpy)", + "hint": None if HAS_AUDIO_SUPPORT else "Install fieldwitness[stego-audio] (soundfile, numpy)", } except ImportError: - capabilities["stego_audio"] = {"status": "missing", "hint": "Install soosef[stego-audio]"} + capabilities["stego_audio"] = {"status": "missing", "hint": "Install fieldwitness[stego-audio]"} # Video steganography try: - from soosef.stegasoo.constants import VIDEO_ENABLED + from fieldwitness.stego.constants import VIDEO_ENABLED capabilities["stego_video"] = { "status": "ok" if VIDEO_ENABLED else "unavailable", "hint": None if VIDEO_ENABLED else "Requires ffmpeg in PATH", @@ -303,33 +303,33 @@ def create_app(config: SoosefConfig | None = None) -> Flask: except (ImportError, AttributeError): capabilities["stego_video"] = {"status": "missing", "hint": "Requires ffmpeg"} - # LMDB (verisoo storage) + # LMDB (attest storage) try: import lmdb # noqa: F401 capabilities["lmdb"] = {"status": "ok"} except ImportError: - capabilities["lmdb"] = {"status": "missing", "hint": "Install soosef[attest]"} + capabilities["lmdb"] = {"status": "missing", "hint": "Install fieldwitness[attest]"} # Perceptual hashing try: import imagehash # noqa: F401 capabilities["imagehash"] = {"status": "ok"} except ImportError: - capabilities["imagehash"] = {"status": "missing", "hint": "Install soosef[attest]"} + capabilities["imagehash"] = {"status": "missing", "hint": "Install fieldwitness[attest]"} # USB monitoring try: import pyudev # noqa: F401 capabilities["usb_monitor"] = {"status": "ok"} except ImportError: - capabilities["usb_monitor"] = {"status": "unavailable", "hint": "Install soosef[fieldkit] (Linux only)"} + capabilities["usb_monitor"] = {"status": "unavailable", "hint": "Install fieldwitness[fieldkit] (Linux only)"} # GPIO (RPi killswitch) try: import gpiozero # noqa: F401 capabilities["gpio"] = {"status": "ok"} except ImportError: - capabilities["gpio"] = {"status": "unavailable", "hint": "Install soosef[rpi] (Raspberry Pi only)"} + capabilities["gpio"] = {"status": "unavailable", "hint": "Install fieldwitness[rpi] (Raspberry Pi only)"} # Key status (existence only, no material) keys = { @@ -350,7 +350,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask: "chain_enabled": config.chain_enabled, } if config.deadman_enabled: - from soosef.fieldkit.deadman import DeadmanSwitch + from fieldwitness.fieldkit.deadman import DeadmanSwitch dm = DeadmanSwitch() dm_status = dm.status() fieldkit["deadman_armed"] = dm_status["armed"] @@ -380,7 +380,7 @@ def create_app(config: SoosefConfig | None = None) -> Flask: return jsonify({ "status": "ok" if all_ok else "degraded", - "version": __import__("soosef").__version__, + "version": __import__("fieldwitness").__version__, "modules": modules, "capabilities": capabilities, "keys": keys, @@ -408,26 +408,26 @@ except ImportError: _HAS_QRCODE_READ = False -# ── Stegasoo route mounting ────────────────────────────────────────── +# ── Stego route mounting ────────────────────────────────────────── -def _register_stegasoo_routes(app: Flask) -> None: +def _register_stego_routes(app: Flask) -> None: """ - Mount all stegasoo web routes into the Flask app. + Mount all stego web routes into the Flask app. Rather than rewriting 3,600 lines of battle-tested route logic, - we import stegasoo's app.py and re-register its routes. - The stegasoo templates are in templates/stego/ and extend our base.html. + we import fieldwitness.stego's app.py and re-register its routes. + The stego templates are in templates/stego/ and extend our base.html. """ import temp_storage from auth import admin_required, login_required - from soosef.stegasoo import ( + from fieldwitness.stego import ( export_rsa_key_pem, generate_credentials, get_channel_status, load_rsa_key, ) - from soosef.stegasoo.constants import ( + from fieldwitness.stego.constants import ( DEFAULT_PASSPHRASE_WORDS, MAX_PIN_LENGTH, MIN_PASSPHRASE_WORDS, @@ -435,7 +435,7 @@ def _register_stegasoo_routes(app: Flask) -> None: TEMP_FILE_EXPIRY, VALID_RSA_SIZES, ) - from soosef.stegasoo.qr_utils import ( + from fieldwitness.stego.qr_utils import ( can_fit_in_qr, generate_qr_code, ) @@ -443,7 +443,7 @@ def _register_stegasoo_routes(app: Flask) -> None: SubprocessStego, ) - from soosef.audit import log_action + from fieldwitness.audit import log_action # Initialize subprocess wrapper subprocess_stego = SubprocessStego(timeout=180) @@ -511,7 +511,7 @@ def _register_stegasoo_routes(app: Flask) -> None: _login_attempts[username] = attempts if len(attempts) >= max_attempts: - from soosef.audit import log_action + from fieldwitness.audit import log_action log_action( actor=username, @@ -818,7 +818,7 @@ def _register_stegasoo_routes(app: Flask) -> None: io.BytesIO(qr_png), mimetype="image/png", as_attachment=True, - download_name="soosef_rsa_key_qr.png", + download_name="fieldwitness_rsa_key_qr.png", ) except Exception as e: return f"Error generating QR code: {e}", 500 @@ -838,7 +838,7 @@ def _register_stegasoo_routes(app: Flask) -> None: private_key = load_rsa_key(key_pem.encode("utf-8")) encrypted_pem = export_rsa_key_pem(private_key, password=password) key_id = secrets.token_hex(4) - filename = f"soosef_key_{private_key.key_size}_{key_id}.pem" + filename = f"fieldwitness_key_{private_key.key_size}_{key_id}.pem" return send_file( io.BytesIO(encrypted_pem), mimetype="application/x-pem-file", diff --git a/frontends/web/auth.py b/frontends/web/auth.py index e8997b3..eb34e00 100644 --- a/frontends/web/auth.py +++ b/frontends/web/auth.py @@ -1,5 +1,5 @@ """ -Stegasoo Authentication Module (v4.1.0) +Stego Authentication Module (v4.1.0) Multi-user authentication with role-based access control. - Admin user created at first-run setup @@ -20,7 +20,7 @@ from argon2 import PasswordHasher from argon2.exceptions import VerifyMismatchError from flask import current_app, flash, g, redirect, session, url_for -# Argon2 password hasher (lighter than stegasoo's 256MB for faster login) +# Argon2 password hasher (lighter than stego's 256MB for faster login) ph = PasswordHasher( time_cost=3, memory_cost=65536, # 64MB @@ -51,8 +51,8 @@ class User: def get_db_path() -> Path: - """Get database path — uses soosef auth directory.""" - from soosef.paths import AUTH_DB + """Get database path — uses fieldwitness auth directory.""" + from fieldwitness.paths import AUTH_DB AUTH_DB.parent.mkdir(parents=True, exist_ok=True) return AUTH_DB @@ -273,7 +273,7 @@ def verify_and_reset_admin_password(recovery_key: str, new_password: str) -> tup Returns: (success, message) tuple """ - from soosef.stegasoo.recovery import verify_recovery_key + from fieldwitness.stego.recovery import verify_recovery_key stored_hash = get_recovery_key_hash() if not stored_hash: diff --git a/frontends/web/blueprints/admin.py b/frontends/web/blueprints/admin.py index c29da14..ca0e25d 100644 --- a/frontends/web/blueprints/admin.py +++ b/frontends/web/blueprints/admin.py @@ -1,4 +1,4 @@ """ -Admin routes are registered directly in app.py via _register_stegasoo_routes() +Admin routes are registered directly in app.py via _register_stego_routes() alongside the auth routes (setup, login, logout, account, admin/users). """ diff --git a/frontends/web/blueprints/attest.py b/frontends/web/blueprints/attest.py index c5c9b6e..afea432 100644 --- a/frontends/web/blueprints/attest.py +++ b/frontends/web/blueprints/attest.py @@ -1,7 +1,7 @@ """ -Attestation blueprint — attest and verify images via Verisoo. +Attestation blueprint — attest and verify images via Attest. -Wraps verisoo's attestation and verification libraries to provide: +Wraps attest's attestation and verification libraries to provide: - Image attestation: upload → hash → sign → store in append-only log - Image verification: upload → hash → search log → display matches - Verification receipt: same as verify but returns a downloadable JSON file @@ -21,27 +21,27 @@ bp = Blueprint("attest", __name__) def _get_storage(): - """Get verisoo LocalStorage pointed at soosef's attestation directory.""" - from soosef.verisoo.storage import LocalStorage + """Get attest LocalStorage pointed at fieldwitness's attestation directory.""" + from fieldwitness.attest.storage import LocalStorage - from soosef.paths import ATTESTATIONS_DIR + from fieldwitness.paths import ATTESTATIONS_DIR return LocalStorage(base_path=ATTESTATIONS_DIR) def _get_private_key(): - """Load the Ed25519 private key from soosef identity directory.""" - from soosef.verisoo.crypto import load_private_key + """Load the Ed25519 private key from fieldwitness identity directory.""" + from fieldwitness.attest.crypto import load_private_key - from soosef.paths import IDENTITY_PRIVATE_KEY + from fieldwitness.paths import IDENTITY_PRIVATE_KEY if not IDENTITY_PRIVATE_KEY.exists(): return None return load_private_key(IDENTITY_PRIVATE_KEY) -def _wrap_in_chain(verisoo_record, private_key, metadata: dict | None = None): - """Wrap a Verisoo attestation record in the hash chain. +def _wrap_in_chain(attest_record, private_key, metadata: dict | None = None): + """Wrap a Attest attestation record in the hash chain. Returns the chain record, or None if chain is disabled. """ @@ -49,23 +49,23 @@ def _wrap_in_chain(verisoo_record, private_key, metadata: dict | None = None): from cryptography.hazmat.primitives.serialization import load_pem_private_key - from soosef.config import SoosefConfig - from soosef.federation.chain import ChainStore - from soosef.paths import CHAIN_DIR, IDENTITY_PRIVATE_KEY + from fieldwitness.config import FieldWitnessConfig + from fieldwitness.federation.chain import ChainStore + from fieldwitness.paths import CHAIN_DIR, IDENTITY_PRIVATE_KEY - config = SoosefConfig.load() + config = FieldWitnessConfig.load() if not config.chain_enabled or not config.chain_auto_wrap: return None - # Hash the verisoo record bytes as chain content + # Hash the attest record bytes as chain content record_bytes = ( - verisoo_record.to_bytes() - if hasattr(verisoo_record, "to_bytes") - else str(verisoo_record).encode() + attest_record.to_bytes() + if hasattr(attest_record, "to_bytes") + else str(attest_record).encode() ) content_hash = hashlib.sha256(record_bytes).digest() - # Load Ed25519 key for chain signing (need the cryptography key, not verisoo's) + # Load Ed25519 key for chain signing (need the cryptography key, not attest's) priv_pem = IDENTITY_PRIVATE_KEY.read_bytes() chain_private_key = load_pem_private_key(priv_pem, password=None) @@ -79,7 +79,7 @@ def _wrap_in_chain(verisoo_record, private_key, metadata: dict | None = None): store = ChainStore(CHAIN_DIR) return store.append( content_hash=content_hash, - content_type="verisoo/attestation-v1", + content_type="attest/attestation-v1", private_key=chain_private_key, metadata=chain_metadata, ) @@ -111,7 +111,7 @@ def attest(): if request.method == "POST": if not has_identity: flash( - "No identity configured. Run 'soosef init' or generate one from the Keys page.", + "No identity configured. Run 'fieldwitness init' or generate one from the Keys page.", "error", ) return redirect(url_for("attest.attest")) @@ -151,7 +151,7 @@ def attest(): # Extract-then-classify: get evidentiary metadata before attestation # so user can control what's included if auto_exif and strip_device: - from soosef.metadata import extract_and_classify + from fieldwitness.metadata import extract_and_classify extraction = extract_and_classify(image_data) # Merge evidentiary fields (GPS, timestamp) but exclude @@ -166,7 +166,7 @@ def attest(): metadata[f"exif_{key}"] = str(value) # Create the attestation - from soosef.verisoo.attestation import create_attestation + from fieldwitness.attest.attestation import create_attestation attestation = create_attestation( image_data=image_data, @@ -194,14 +194,14 @@ def attest(): # Save our own identity so we can look it up during verification from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat - from soosef.verisoo.models import Identity + from fieldwitness.attest.models import Identity pub_key = private_key.public_key() pub_bytes = pub_key.public_bytes(Encoding.Raw, PublicFormat.Raw) identity = Identity( public_key=pub_bytes, fingerprint=attestation.record.attestor_fingerprint, - metadata={"name": "SooSeF Local Identity"}, + metadata={"name": "FieldWitness Local Identity"}, ) try: storage.save_identity(identity) @@ -246,11 +246,11 @@ def attest_batch(): """ import hashlib - from soosef.verisoo.hashing import hash_image + from fieldwitness.attest.hashing import hash_image private_key = _get_private_key() if private_key is None: - return {"error": "No identity key. Run soosef init first."}, 400 + return {"error": "No identity key. Run fieldwitness init first."}, 400 files = request.files.getlist("images") if not files: @@ -271,14 +271,14 @@ def attest_batch(): results.append({"file": filename, "status": "skipped", "reason": "already attested"}) continue - from soosef.verisoo.attestation import create_attestation + from fieldwitness.attest.attestation import create_attestation attestation = create_attestation(image_data, private_key) index = storage.append_record(attestation.record) # Wrap in chain if enabled chain_index = None - config = request.app.config.get("SOOSEF_CONFIG") if hasattr(request, "app") else None + config = request.app.config.get("FIELDWITNESS_CONFIG") if hasattr(request, "app") else None if config and getattr(config, "chain_enabled", False) and getattr(config, "chain_auto_wrap", False): try: chain_record = _wrap_in_chain(attestation.record, private_key, {}) @@ -365,11 +365,11 @@ def _verify_image(image_data: bytes) -> dict: """Run the full verification pipeline against the attestation log. Returns a dict with keys: - query_hashes — ImageHashes object from verisoo + query_hashes — ImageHashes object from fieldwitness.attest matches — list of match dicts (record, match_type, distances, attestor_name) record_count — total records searched """ - from soosef.verisoo.hashing import compute_all_distances, hash_image, is_same_image + from fieldwitness.attest.hashing import compute_all_distances, hash_image, is_same_image query_hashes = hash_image(image_data) storage = _get_storage() @@ -541,12 +541,12 @@ def verify_receipt(): # Chain position proof — look up this attestation in the hash chain try: - from soosef.config import SoosefConfig - from soosef.federation.chain import ChainStore - from soosef.federation.serialization import compute_record_hash - from soosef.paths import CHAIN_DIR + from fieldwitness.config import FieldWitnessConfig + from fieldwitness.federation.chain import ChainStore + from fieldwitness.federation.serialization import compute_record_hash + from fieldwitness.paths import CHAIN_DIR - chain_config = SoosefConfig.load() + chain_config = FieldWitnessConfig.load() if chain_config.chain_enabled: chain_store = ChainStore(CHAIN_DIR) # Search chain for a record whose content_hash matches this attestation diff --git a/frontends/web/blueprints/dropbox.py b/frontends/web/blueprints/dropbox.py index 7a5197f..0835f6e 100644 --- a/frontends/web/blueprints/dropbox.py +++ b/frontends/web/blueprints/dropbox.py @@ -1,7 +1,7 @@ """ Source drop box blueprint — anonymous, token-gated file submission. -Provides a SecureDrop-like intake that lives inside SooSeF: +Provides a SecureDrop-like intake that lives inside FieldWitness: - Admin creates a time-limited upload token - Source opens the token URL in a browser (no account needed) - Files are uploaded, EXIF-stripped, and auto-attested on receipt @@ -21,8 +21,8 @@ from pathlib import Path from auth import admin_required, login_required from flask import Blueprint, Response, flash, redirect, render_template, request, url_for -from soosef.audit import log_action -from soosef.paths import AUTH_DIR, TEMP_DIR +from fieldwitness.audit import log_action +from fieldwitness.paths import AUTH_DIR, TEMP_DIR bp = Blueprint("dropbox", __name__, url_prefix="/dropbox") @@ -188,7 +188,7 @@ def upload(token): # 1. Extract EXIF into attestation metadata (evidentiary fields) # 2. Attest the ORIGINAL bytes (hash matches what source submitted) # 3. Strip metadata from the stored copy (protect source device info) - from soosef.metadata import extract_strip_pipeline + from fieldwitness.metadata import extract_strip_pipeline extraction, stripped_data = extract_strip_pipeline(raw_data) @@ -204,7 +204,7 @@ def upload(token): # is preserved in the attestation metadata; dangerous fields # (device serial) are excluded. try: - from soosef.verisoo.attestation import create_attestation + from fieldwitness.attest.attestation import create_attestation from blueprints.attest import _get_private_key, _get_storage @@ -240,7 +240,7 @@ def upload(token): # insufficient), making valid receipts unforgeable. import hmac - from soosef.paths import SECRET_KEY_FILE + from fieldwitness.paths import SECRET_KEY_FILE server_secret = SECRET_KEY_FILE.read_bytes() if SECRET_KEY_FILE.exists() else token.encode() receipt_code = hmac.new( @@ -279,7 +279,7 @@ def upload(token): return Response(receipt_text, content_type="text/plain") # GET — show upload form with client-side SHA-256 hashing - # Minimal page, no SooSeF branding (source safety) + # Minimal page, no FieldWitness branding (source safety) remaining = token_data["max_files"] - token_data["used"] return f""" Secure Upload diff --git a/frontends/web/blueprints/federation.py b/frontends/web/blueprints/federation.py index e5c733b..4656034 100644 --- a/frontends/web/blueprints/federation.py +++ b/frontends/web/blueprints/federation.py @@ -12,7 +12,7 @@ bp = Blueprint("federation", __name__, url_prefix="/federation") @login_required def status(): """Federation status dashboard.""" - from soosef.verisoo.peer_store import PeerStore + from fieldwitness.attest.peer_store import PeerStore store = PeerStore() peers = store.list_peers() @@ -21,9 +21,9 @@ def status(): # Get local node info node_info = {"root": None, "size": 0} try: - from soosef.verisoo.storage import LocalStorage + from fieldwitness.attest.storage import LocalStorage - import soosef.paths as _paths + import fieldwitness.paths as _paths storage = LocalStorage(_paths.ATTESTATIONS_DIR) stats = storage.get_stats() @@ -48,7 +48,7 @@ def status(): @admin_required def peer_add(): """Add a federation peer.""" - from soosef.verisoo.peer_store import PeerStore + from fieldwitness.attest.peer_store import PeerStore url = request.form.get("url", "").strip() fingerprint = request.form.get("fingerprint", "").strip() @@ -67,7 +67,7 @@ def peer_add(): @admin_required def peer_remove(): """Remove a federation peer.""" - from soosef.verisoo.peer_store import PeerStore + from fieldwitness.attest.peer_store import PeerStore url = request.form.get("url", "").strip() store = PeerStore() diff --git a/frontends/web/blueprints/fieldkit.py b/frontends/web/blueprints/fieldkit.py index 4a93862..8f9a4e8 100644 --- a/frontends/web/blueprints/fieldkit.py +++ b/frontends/web/blueprints/fieldkit.py @@ -5,7 +5,7 @@ Fieldkit blueprint — killswitch, dead man's switch, status dashboard. from auth import admin_required, get_username, login_required from flask import Blueprint, flash, redirect, render_template, request, url_for -from soosef.audit import log_action +from fieldwitness.audit import log_action bp = Blueprint("fieldkit", __name__, url_prefix="/fieldkit") @@ -14,7 +14,7 @@ bp = Blueprint("fieldkit", __name__, url_prefix="/fieldkit") @login_required def status(): """Fieldkit status dashboard — all monitors and system health.""" - from soosef.fieldkit.deadman import DeadmanSwitch + from fieldwitness.fieldkit.deadman import DeadmanSwitch deadman = DeadmanSwitch() return render_template( @@ -39,7 +39,7 @@ def killswitch(): flash("Killswitch requires password confirmation.", "danger") return render_template("fieldkit/killswitch.html") - from soosef.fieldkit.killswitch import PurgeScope, execute_purge + from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge actor = username result = execute_purge(PurgeScope.ALL, reason="web_ui") @@ -71,7 +71,7 @@ def killswitch(): @login_required def deadman_checkin(): """Record a dead man's switch check-in.""" - from soosef.fieldkit.deadman import DeadmanSwitch + from fieldwitness.fieldkit.deadman import DeadmanSwitch deadman = DeadmanSwitch() deadman.checkin() diff --git a/frontends/web/blueprints/keys.py b/frontends/web/blueprints/keys.py index f26e481..dc6075b 100644 --- a/frontends/web/blueprints/keys.py +++ b/frontends/web/blueprints/keys.py @@ -5,7 +5,7 @@ Key management blueprint — unified view of all key material. from auth import get_username, login_required from flask import Blueprint, flash, redirect, render_template, url_for -from soosef.audit import log_action +from fieldwitness.audit import log_action bp = Blueprint("keys", __name__, url_prefix="/keys") @@ -14,7 +14,7 @@ bp = Blueprint("keys", __name__, url_prefix="/keys") @login_required def index(): """Key management dashboard.""" - from soosef.keystore import KeystoreManager + from fieldwitness.keystore import KeystoreManager ks = KeystoreManager() return render_template("fieldkit/keys.html", keystore=ks.status()) @@ -24,7 +24,7 @@ def index(): @login_required def generate_channel(): """Generate a new channel key.""" - from soosef.keystore import KeystoreManager + from fieldwitness.keystore import KeystoreManager ks = KeystoreManager() try: @@ -54,7 +54,7 @@ def generate_channel(): @login_required def generate_identity(): """Generate a new Ed25519 identity.""" - from soosef.keystore import KeystoreManager + from fieldwitness.keystore import KeystoreManager ks = KeystoreManager() try: diff --git a/frontends/web/blueprints/stego.py b/frontends/web/blueprints/stego.py index 2981f83..85ee024 100644 --- a/frontends/web/blueprints/stego.py +++ b/frontends/web/blueprints/stego.py @@ -1,8 +1,8 @@ """ -Steganography routes are registered directly in app.py via _register_stegasoo_routes() -rather than as a blueprint, because the stegasoo route logic (3,600+ lines) uses +Steganography routes are registered directly in app.py via _register_stego_routes() +rather than as a blueprint, because the stego route logic (3,600+ lines) uses module-level state (ThreadPoolExecutor, jobs dict, subprocess_stego instance) that doesn't translate cleanly to a blueprint. -The stego templates are in templates/stego/ and extend the soosef base.html. +The stego templates are in templates/stego/ and extend the fieldwitness base.html. """ diff --git a/frontends/web/ssl_utils.py b/frontends/web/ssl_utils.py index a07ea25..b88775f 100644 --- a/frontends/web/ssl_utils.py +++ b/frontends/web/ssl_utils.py @@ -83,7 +83,7 @@ def generate_self_signed_cert( # Create certificate subject = issuer = x509.Name( [ - x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Stegasoo"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "FieldWitness"), x509.NameAttribute(NameOID.COMMON_NAME, hostname), ] ) diff --git a/frontends/web/static/js/auth.js b/frontends/web/static/js/auth.js index a04edf6..98eb782 100644 --- a/frontends/web/static/js/auth.js +++ b/frontends/web/static/js/auth.js @@ -1,9 +1,9 @@ /** - * Stegasoo Authentication Pages JavaScript + * FieldWitness Authentication Pages JavaScript * Handles login, setup, account, and admin user management pages */ -const StegasooAuth = { +const StegoAuth = { // ======================================================================== // PASSWORD VISIBILITY TOGGLE @@ -128,15 +128,15 @@ const StegasooAuth = { // Make togglePassword available globally for onclick handlers function togglePassword(inputId, btn) { - StegasooAuth.togglePassword(inputId, btn); + StegoAuth.togglePassword(inputId, btn); } // Make copyField available globally for onclick handlers function copyField(fieldId) { - StegasooAuth.copyField(fieldId); + StegoAuth.copyField(fieldId); } // Make regeneratePassword available globally for onclick handlers function regeneratePassword() { - StegasooAuth.regeneratePassword(); + StegoAuth.regeneratePassword(); } diff --git a/frontends/web/static/js/soosef.js b/frontends/web/static/js/fieldwitness.js similarity index 98% rename from frontends/web/static/js/soosef.js rename to frontends/web/static/js/fieldwitness.js index 75eff42..8e5f0ed 100644 --- a/frontends/web/static/js/soosef.js +++ b/frontends/web/static/js/fieldwitness.js @@ -1,9 +1,9 @@ /** - * Stegasoo Frontend JavaScript + * FieldWitness Frontend JavaScript * Shared functionality across encode, decode, and generate pages */ -const Stegasoo = { +const Stego = { // ======================================================================== // PASSWORD/PIN VISIBILITY TOGGLES @@ -97,10 +97,10 @@ const Stegasoo = { if (this.files && this.files[0]) { const file = this.files[0]; if (file.type.startsWith('image/') && preview) { - Stegasoo.showImagePreview(file, preview, label, zone); + Stego.showImagePreview(file, preview, label, zone); } else if (file.type.startsWith('audio/') || !file.type.startsWith('image/')) { // Audio or non-image files: show file info instead of image preview - Stegasoo.showAudioFileInfo(file, zone); + Stego.showAudioFileInfo(file, zone); if (label) { label.classList.add('d-none'); } @@ -155,9 +155,9 @@ const Stegasoo = { // Trigger appropriate animation if (isScanContainer) { - Stegasoo.triggerScanAnimation(zone, file); + Stego.triggerScanAnimation(zone, file); } else if (isPixelContainer) { - Stegasoo.triggerPixelReveal(zone, file); + Stego.triggerPixelReveal(zone, file); } }; reader.readAsDataURL(file); @@ -264,7 +264,7 @@ const Stegasoo = { if (hashEl) { // Generate a deterministic fake hash preview from filename + size - const fakeHash = Stegasoo.generateFakeHash(file.name + file.size); + const fakeHash = Stego.generateFakeHash(file.name + file.size); hashEl.textContent = `SHA256: ${fakeHash.substring(0, 8)}····${fakeHash.substring(56)}`; } } @@ -328,7 +328,7 @@ const Stegasoo = { tracesContainer.style.left = imgLeft + 'px'; // Generate Tron-style circuit traces covering the image - Stegasoo.generateEmbedTraces(tracesContainer, imgWidth, imgHeight); + Stego.generateEmbedTraces(tracesContainer, imgWidth, imgHeight); }; // Wait for image to be ready @@ -349,7 +349,7 @@ const Stegasoo = { if (grid) grid.remove(); // Populate data panel - Stegasoo.populatePixelDataPanel(container, file, preview); + Stego.populatePixelDataPanel(container, file, preview); }, duration); }, @@ -453,7 +453,7 @@ const Stegasoo = { input.addEventListener('change', function() { if (this.files && this.files[0]) { - Stegasoo.showImagePreview(this.files[0], preview, label, container); + Stego.showImagePreview(this.files[0], preview, label, container); } }); }); @@ -1721,10 +1721,10 @@ const Stegasoo = { document.addEventListener('DOMContentLoaded', () => { // Detect page and initialize if (document.getElementById('encodeForm')) { - Stegasoo.initEncodePage(); + Stego.initEncodePage(); } else if (document.getElementById('decodeForm')) { - Stegasoo.initDecodePage(); + Stego.initDecodePage(); } else if (document.querySelector('[data-page="generate"]')) { - Stegasoo.initGeneratePage(); + Stego.initGeneratePage(); } }); diff --git a/frontends/web/static/js/generate.js b/frontends/web/static/js/generate.js index 1e737a4..13fe5c5 100644 --- a/frontends/web/static/js/generate.js +++ b/frontends/web/static/js/generate.js @@ -1,9 +1,9 @@ /** - * Stegasoo Generate Page JavaScript + * FieldWitness Stego Generate Page JavaScript * Handles credential generation form and display */ -const StegasooGenerate = { +const StegoGenerate = { // ======================================================================== // FORM CONTROLS @@ -260,20 +260,20 @@ const StegasooGenerate = { // Global function wrappers for onclick handlers function togglePinVisibility() { - StegasooGenerate.togglePinVisibility(); + StegoGenerate.togglePinVisibility(); } function togglePassphraseVisibility() { - StegasooGenerate.togglePassphraseVisibility(); + StegoGenerate.togglePassphraseVisibility(); } function printQrCode() { - StegasooGenerate.printQrCode(); + StegoGenerate.printQrCode(); } // Auto-init form controls document.addEventListener('DOMContentLoaded', () => { if (document.querySelector('[data-page="generate"]')) { - StegasooGenerate.initForm(); + StegoGenerate.initForm(); } }); diff --git a/frontends/web/static/style.css b/frontends/web/static/style.css index d693d0e..972d5fb 100644 --- a/frontends/web/static/style.css +++ b/frontends/web/static/style.css @@ -1,6 +1,6 @@ /* ============================================================================ - SooSeF - Main Stylesheet - Adapted from Stegasoo's style.css — same dark theme, same patterns. + FieldWitness - Main Stylesheet + Dark theme stylesheet for the FieldWitness web UI. ============================================================================ */ :root { @@ -26,7 +26,7 @@ letter-spacing: 0.05em; } -/* Nav icon + label pattern from stegasoo */ +/* Nav icon + label pattern */ .nav-icons .nav-link { display: flex; align-items: center; diff --git a/frontends/web/stego_routes.py b/frontends/web/stego_routes.py index 4a67dd5..4318a78 100644 --- a/frontends/web/stego_routes.py +++ b/frontends/web/stego_routes.py @@ -1,7 +1,7 @@ """ -Stegasoo encode/decode/tools routes. +Stego encode/decode/tools routes. -Ported from stegasoo's frontends/web/app.py. These routes handle: +Ported from fieldwitness.stego's frontends/web/app.py. These routes handle: - Image encode with async progress tracking - Audio encode (v4.3.0) - Image/audio decode @@ -33,7 +33,7 @@ from PIL import Image def register_stego_routes(app, **deps): - """Register all stegasoo encode/decode routes on the Flask app.""" + """Register all stego encode/decode routes on the Flask app.""" # Unpack dependencies passed from app.py login_required = deps["login_required"] @@ -41,7 +41,7 @@ def register_stego_routes(app, **deps): temp_storage = deps["temp_storage"] _has_qrcode_read = deps.get("has_qrcode_read", False) - from soosef.stegasoo import ( + from fieldwitness.stego import ( HAS_AUDIO_SUPPORT, CapacityError, DecryptionError, @@ -49,7 +49,7 @@ def register_stego_routes(app, **deps): InvalidHeaderError, InvalidMagicBytesError, ReedSolomonError, - StegasooError, + StegoError, generate_filename, has_dct_support, validate_file_payload, @@ -60,13 +60,13 @@ def register_stego_routes(app, **deps): validate_rsa_key, validate_security_factors, ) - from soosef.stegasoo.channel import resolve_channel_key - from soosef.stegasoo.constants import ( + from fieldwitness.stego.channel import resolve_channel_key + from fieldwitness.stego.constants import ( TEMP_FILE_EXPIRY, THUMBNAIL_QUALITY, THUMBNAIL_SIZE, ) - from soosef.stegasoo.qr_utils import ( + from fieldwitness.stego.qr_utils import ( decompress_data, extract_key_from_qr, is_compressed, @@ -152,7 +152,7 @@ def register_stego_routes(app, **deps): else: return f"{n/(1024*1024):.1f} MB" - # ── Routes below are extracted from stegasoo app.py ── + # ── Routes below are extracted from fieldwitness.stego app.py ── def _run_encode_job(job_id: str, encode_params: dict) -> None: """Background thread function for async encode.""" @@ -686,7 +686,7 @@ def register_stego_routes(app, **deps): return _error_response(result.error_message) # Pre-check payload capacity BEFORE encode (fail fast) - from soosef.stegasoo.steganography import will_fit_by_mode + from fieldwitness.stego.steganography import will_fit_by_mode payload_size = ( len(payload.data) if hasattr(payload, "data") else len(payload.encode("utf-8")) @@ -770,7 +770,7 @@ def register_stego_routes(app, **deps): error_msg = encode_result.error or "Encoding failed" if "capacity" in error_msg.lower(): raise CapacityError(error_msg) - raise StegasooError(error_msg) + raise StegoError(error_msg) # Determine actual output format for filename and storage if embed_mode == "dct" and dct_output_format == "jpeg": @@ -813,7 +813,7 @@ def register_stego_routes(app, **deps): except CapacityError as e: return _error_response(str(e)) - except StegasooError as e: + except StegoError as e: return _error_response(str(e)) except Exception as e: return _error_response(f"Error: {e}") @@ -1443,7 +1443,7 @@ def register_stego_routes(app, **deps): or decode_result.error_type == "DecryptionError" ): raise DecryptionError(error_msg) - raise StegasooError(error_msg) + raise StegoError(error_msg) if decode_result.is_file: # File content - store temporarily for download @@ -1479,7 +1479,7 @@ def register_stego_routes(app, **deps): except InvalidMagicBytesError: flash( - "This doesn't appear to be a Stegasoo image. Try a different mode (LSB/DCT).", + "This doesn't appear to be a Stego image. Try a different mode (LSB/DCT).", "warning", ) return render_template("stego/decode.html", has_qrcode_read=_has_qrcode_read) @@ -1501,7 +1501,7 @@ def register_stego_routes(app, **deps): "warning", ) return render_template("stego/decode.html", has_qrcode_read=_has_qrcode_read) - except StegasooError as e: + except StegoError as e: flash(str(e), "error") return render_template("stego/decode.html", has_qrcode_read=_has_qrcode_read) except Exception as e: @@ -1613,8 +1613,8 @@ def register_stego_routes(app, **deps): @app.route("/about") def about(): from auth import get_current_user - from soosef.stegasoo import has_argon2 - from soosef.stegasoo.channel import get_channel_status + from fieldwitness.stego import has_argon2 + from fieldwitness.stego.channel import get_channel_status channel_status = get_channel_status() current_user = get_current_user() @@ -1644,7 +1644,7 @@ def register_stego_routes(app, **deps): @login_required def api_tools_capacity(): """Calculate image capacity for steganography.""" - from soosef.stegasoo.dct_steganography import estimate_capacity_comparison + from fieldwitness.stego.dct_steganography import estimate_capacity_comparison carrier = request.files.get("image") if not carrier: @@ -1666,7 +1666,7 @@ def register_stego_routes(app, **deps): """Strip EXIF/metadata from image.""" import io - from soosef.stegasoo.utils import strip_image_metadata + from fieldwitness.stego.utils import strip_image_metadata image_file = request.files.get("image") if not image_file: @@ -1689,7 +1689,7 @@ def register_stego_routes(app, **deps): @login_required def api_tools_exif(): """Read EXIF metadata from image.""" - from soosef.stegasoo.utils import read_image_exif + from fieldwitness.stego.utils import read_image_exif image_file = request.files.get("image") if not image_file: @@ -1718,7 +1718,7 @@ def register_stego_routes(app, **deps): @login_required def api_tools_exif_update(): """Update EXIF fields in image.""" - from soosef.stegasoo.utils import write_image_exif + from fieldwitness.stego.utils import write_image_exif image_file = request.files.get("image") if not image_file: @@ -1757,7 +1757,7 @@ def register_stego_routes(app, **deps): @login_required def api_tools_exif_clear(): """Remove all EXIF metadata from image.""" - from soosef.stegasoo.utils import strip_image_metadata + from fieldwitness.stego.utils import strip_image_metadata image_file = request.files.get("image") if not image_file: @@ -2062,7 +2062,7 @@ def register_stego_routes(app, **deps): @app.route("/test-capacity", methods=["POST"]) def test_capacity(): - """Minimal capacity test - no stegasoo code, just PIL.""" + """Minimal capacity test - no stego code, just PIL.""" carrier = request.files.get("carrier") if not carrier: return jsonify({"error": "No carrier image provided"}), 400 @@ -2095,7 +2095,7 @@ def register_stego_routes(app, **deps): @app.route("/test-capacity-nopil", methods=["POST"]) def test_capacity_nopil(): - """Ultra-minimal test - no PIL, no stegasoo.""" + """Ultra-minimal test - no PIL, no stego.""" carrier = request.files.get("carrier") if not carrier: return jsonify({"error": "No carrier image provided"}), 400 diff --git a/frontends/web/stego_worker.py b/frontends/web/stego_worker.py index e988ebc..68b5a78 100644 --- a/frontends/web/stego_worker.py +++ b/frontends/web/stego_worker.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Stegasoo Subprocess Worker (v4.0.0) +Stego Subprocess Worker (v4.0.0) This script runs in a subprocess and handles encode/decode operations. If it crashes due to jpeglib/scipy issues, the parent Flask process survives. @@ -25,12 +25,12 @@ import sys import traceback from pathlib import Path -# Ensure stegasoo is importable +# Ensure stego is importable sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) sys.path.insert(0, str(Path(__file__).parent)) # Configure logging for worker subprocess -_log_level = os.environ.get("STEGASOO_LOG_LEVEL", "").strip().upper() +_log_level = os.environ.get("FIELDWITNESS_LOG_LEVEL", "").strip().upper() if _log_level and hasattr(logging, _log_level): logging.basicConfig( level=getattr(logging, _log_level), @@ -38,19 +38,19 @@ if _log_level and hasattr(logging, _log_level): datefmt="%H:%M:%S", stream=sys.stderr, ) -elif os.environ.get("STEGASOO_DEBUG", "").strip() in ("1", "true", "yes"): +elif os.environ.get("FIELDWITNESS_DEBUG", "").strip() in ("1", "true", "yes"): logging.basicConfig( level=logging.DEBUG, format="[%(asctime)s.%(msecs)03d] [%(levelname)s] [%(name)s] %(message)s", datefmt="%H:%M:%S", stream=sys.stderr, ) -logger = logging.getLogger("stegasoo.worker") +logger = logging.getLogger("stego.worker") def _resolve_channel_key(channel_key_param): """ - Resolve channel_key parameter to value for stegasoo. + Resolve channel_key parameter to value for stego. Args: channel_key_param: 'auto', 'none', explicit key, or None @@ -73,7 +73,7 @@ def _get_channel_info(resolved_key): Returns: (mode, fingerprint) tuple """ - from soosef.stegasoo import get_channel_status, has_channel_key + from fieldwitness.stego import get_channel_status, has_channel_key if resolved_key == "": return "public", None @@ -94,7 +94,7 @@ def _get_channel_info(resolved_key): def encode_operation(params: dict) -> dict: """Handle encode operation.""" logger.debug("encode_operation: mode=%s", params.get("embed_mode", "lsb")) - from soosef.stegasoo import FilePayload, encode + from fieldwitness.stego import FilePayload, encode # Decode base64 inputs carrier_data = base64.b64decode(params["carrier_b64"]) @@ -173,7 +173,7 @@ def _write_decode_progress(progress_file: str | None, percent: int, phase: str) def decode_operation(params: dict) -> dict: """Handle decode operation.""" logger.debug("decode_operation: mode=%s", params.get("embed_mode", "auto")) - from soosef.stegasoo import decode + from fieldwitness.stego import decode progress_file = params.get("progress_file") @@ -227,7 +227,7 @@ def decode_operation(params: dict) -> dict: def compare_operation(params: dict) -> dict: """Handle compare_modes operation.""" - from soosef.stegasoo import compare_modes + from fieldwitness.stego import compare_modes carrier_data = base64.b64decode(params["carrier_b64"]) result = compare_modes(carrier_data) @@ -240,7 +240,7 @@ def compare_operation(params: dict) -> dict: def capacity_check_operation(params: dict) -> dict: """Handle will_fit_by_mode operation.""" - from soosef.stegasoo import will_fit_by_mode + from fieldwitness.stego import will_fit_by_mode carrier_data = base64.b64decode(params["carrier_b64"]) @@ -259,7 +259,7 @@ def capacity_check_operation(params: dict) -> dict: def encode_audio_operation(params: dict) -> dict: """Handle audio encode operation (v4.3.0).""" logger.debug("encode_audio_operation: mode=%s", params.get("embed_mode", "audio_lsb")) - from soosef.stegasoo import FilePayload, encode_audio + from fieldwitness.stego import FilePayload, encode_audio carrier_data = base64.b64decode(params["carrier_b64"]) reference_data = base64.b64decode(params["reference_b64"]) @@ -324,7 +324,7 @@ def encode_audio_operation(params: dict) -> dict: def decode_audio_operation(params: dict) -> dict: """Handle audio decode operation (v4.3.0).""" logger.debug("decode_audio_operation: mode=%s", params.get("embed_mode", "audio_auto")) - from soosef.stegasoo import decode_audio + from fieldwitness.stego import decode_audio progress_file = params.get("progress_file") _write_decode_progress(progress_file, 5, "reading") @@ -370,9 +370,9 @@ def decode_audio_operation(params: dict) -> dict: def audio_info_operation(params: dict) -> dict: """Handle audio info operation (v4.3.0).""" - from soosef.stegasoo import get_audio_info - from soosef.stegasoo.audio_steganography import calculate_audio_lsb_capacity - from soosef.stegasoo.spread_steganography import calculate_audio_spread_capacity + from fieldwitness.stego import get_audio_info + from fieldwitness.stego.audio_steganography import calculate_audio_lsb_capacity + from fieldwitness.stego.spread_steganography import calculate_audio_spread_capacity audio_data = base64.b64decode(params["audio_b64"]) @@ -397,7 +397,7 @@ def audio_info_operation(params: dict) -> dict: def channel_status_operation(params: dict) -> dict: """Handle channel status check (v4.0.0).""" - from soosef.stegasoo import get_channel_status + from fieldwitness.stego import get_channel_status status = get_channel_status() reveal = params.get("reveal", False) diff --git a/frontends/web/subprocess_stego.py b/frontends/web/subprocess_stego.py index c9d8f5e..a2edb0d 100644 --- a/frontends/web/subprocess_stego.py +++ b/frontends/web/subprocess_stego.py @@ -1,7 +1,7 @@ """ Subprocess Steganography Wrapper (v4.0.0) -Runs stegasoo operations in isolated subprocesses to prevent crashes +Runs stego operations in isolated subprocesses to prevent crashes from taking down the Flask server. CHANGES in v4.0.0: @@ -743,7 +743,7 @@ def generate_job_id() -> str: def get_progress_file_path(job_id: str) -> str: """Get the progress file path for a job ID.""" - return str(Path(tempfile.gettempdir()) / f"stegasoo_progress_{job_id}.json") + return str(Path(tempfile.gettempdir()) / f"stego_progress_{job_id}.json") def read_progress(job_id: str) -> dict | None: diff --git a/frontends/web/temp_storage.py b/frontends/web/temp_storage.py index ca66b8e..53a4be0 100644 --- a/frontends/web/temp_storage.py +++ b/frontends/web/temp_storage.py @@ -12,7 +12,7 @@ Files are stored in a temp directory with: IMPORTANT: This module ONLY manages files in the temp directory. It does NOT touch instance/ (auth database) or any other directories. -All temp files are written to ~/.soosef/temp/ (soosef.paths.TEMP_DIR) so +All temp files are written to ~/.fieldwitness/temp/ (fieldwitness.paths.TEMP_DIR) so that the killswitch's destroy_temp_files step covers them. """ @@ -24,9 +24,9 @@ import time from pathlib import Path from threading import Lock -import soosef.paths as paths +import fieldwitness.paths as paths -# Default temp directory — always under ~/.soosef/temp/ so the killswitch +# Default temp directory — always under ~/.fieldwitness/temp/ so the killswitch # (which purges paths.TEMP_DIR) can reach every file written here. DEFAULT_TEMP_DIR: Path = paths.TEMP_DIR diff --git a/frontends/web/templates/account.html b/frontends/web/templates/account.html index c891a66..67945e3 100644 --- a/frontends/web/templates/account.html +++ b/frontends/web/templates/account.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Account - Stegasoo{% endblock %} +{% block title %}Account - Stego{% endblock %} {% block content %}
@@ -269,16 +269,16 @@ {% block scripts %} - + {% if is_admin %} {% endif %} {% endblock %} diff --git a/frontends/web/templates/regenerate_recovery.html b/frontends/web/templates/regenerate_recovery.html index 78df441..887b319 100644 --- a/frontends/web/templates/regenerate_recovery.html +++ b/frontends/web/templates/regenerate_recovery.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Regenerate Recovery Key - Stegasoo{% endblock %} +{% block title %}Regenerate Recovery Key - Stego{% endblock %} {% block content %}
@@ -142,7 +142,7 @@ function copyToClipboard() { // Download as text file function downloadTextFile() { const key = document.getElementById('recoveryKey').value; - const content = `Stegasoo Recovery Key + const content = `Stego Recovery Key ===================== ${key} @@ -158,7 +158,7 @@ Generated: ${new Date().toISOString()} const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = 'stegasoo-recovery-key.txt'; + a.download = 'stego-recovery-key.txt'; a.click(); URL.revokeObjectURL(url); } @@ -170,7 +170,7 @@ function downloadQRImage() { const a = document.createElement('a'); a.href = img.src; - a.download = 'stegasoo-recovery-qr.png'; + a.download = 'stego-recovery-qr.png'; a.click(); } diff --git a/frontends/web/templates/setup.html b/frontends/web/templates/setup.html index 260cd63..a4bbff6 100644 --- a/frontends/web/templates/setup.html +++ b/frontends/web/templates/setup.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Setup - Stegasoo{% endblock %} +{% block title %}Setup - Stego{% endblock %} {% block content %}
@@ -12,7 +12,7 @@

- Welcome to Stegasoo! Create your admin account to get started. + Welcome to Stego! Create your admin account to get started.

@@ -72,6 +72,6 @@ {% block scripts %} {% endblock %} diff --git a/frontends/web/templates/setup_recovery.html b/frontends/web/templates/setup_recovery.html index 5f2fd97..30bbe28 100644 --- a/frontends/web/templates/setup_recovery.html +++ b/frontends/web/templates/setup_recovery.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Recovery Key Setup - Stegasoo{% endblock %} +{% block title %}Recovery Key Setup - Stego{% endblock %} {% block content %}
@@ -135,7 +135,7 @@ function copyToClipboard() { // Download as text file function downloadTextFile() { const key = document.getElementById('recoveryKey').value; - const content = `Stegasoo Recovery Key + const content = `Stego Recovery Key ===================== ${key} @@ -151,7 +151,7 @@ Generated: ${new Date().toISOString()} const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = 'stegasoo-recovery-key.txt'; + a.download = 'stego-recovery-key.txt'; a.click(); URL.revokeObjectURL(url); } @@ -163,7 +163,7 @@ function downloadQRImage() { const a = document.createElement('a'); a.href = img.src; - a.download = 'stegasoo-recovery-qr.png'; + a.download = 'stego-recovery-qr.png'; a.click(); } diff --git a/frontends/web/templates/stego/about.html b/frontends/web/templates/stego/about.html index 9686b9e..792a9f4 100644 --- a/frontends/web/templates/stego/about.html +++ b/frontends/web/templates/stego/about.html @@ -1,17 +1,17 @@ {% extends "base.html" %} -{% block title %}About - Stegasoo{% endblock %} +{% block title %}About - Stego{% endblock %} {% block content %}
-
About Stegasoo
+
About Stego

- Stegasoo hides encrypted messages and files inside images using multi-factor authentication. + Stego hides encrypted messages and files inside images using multi-factor authentication.

Features
diff --git a/frontends/web/templates/stego/decode.html b/frontends/web/templates/stego/decode.html index 36c7c89..c7c9ef9 100644 --- a/frontends/web/templates/stego/decode.html +++ b/frontends/web/templates/stego/decode.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}Decode Message - Stegasoo{% endblock %} +{% block title %}Decode Message - Stego{% endblock %} {% block content %}