Rebrand SooSeF to FieldWitness

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) <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-04-02 15:05:13 -04:00
parent 6325e86873
commit 490f9d4a1d
188 changed files with 4588 additions and 2017 deletions

View File

@@ -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",

View File

@@ -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:

View File

@@ -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).
"""

View File

@@ -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

View File

@@ -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"""<!DOCTYPE html>
<html><head><title>Secure Upload</title>

View File

@@ -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()

View File

@@ -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()

View File

@@ -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:

View File

@@ -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.
"""

View File

@@ -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),
]
)

View File

@@ -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();
}

View File

@@ -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();
}
});

View File

@@ -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();
}
});

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Account - Stegasoo{% endblock %}
{% block title %}Account - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">
@@ -269,16 +269,16 @@
{% block scripts %}
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
{% if is_admin %}
<script src="{{ url_for('static', filename='js/qrcode.min.js') }}"></script>
{% endif %}
<script>
StegasooAuth.initPasswordConfirmation('accountForm', 'newPasswordInput', 'newPasswordConfirmInput');
StegoAuth.initPasswordConfirmation('accountForm', 'newPasswordInput', 'newPasswordConfirmInput');
// Webcam QR scanning for channel key input (v4.1.5)
document.getElementById('scanChannelKeyBtn')?.addEventListener('click', function() {
Stegasoo.showQrScanner((text) => {
Stego.showQrScanner((text) => {
const input = document.getElementById('channelKeyInput');
if (input) {
// Clean and format the key
@@ -294,7 +294,7 @@ document.getElementById('scanChannelKeyBtn')?.addEventListener('click', function
// Format channel key input as user types
document.getElementById('channelKeyInput')?.addEventListener('input', function() {
Stegasoo.formatChannelKeyInput(this);
Stego.formatChannelKeyInput(this);
});
function renameKey(keyId, currentName) {
@@ -336,7 +336,7 @@ document.getElementById('qrDownload')?.addEventListener('click', function() {
const keyName = document.getElementById('qrKeyName').textContent;
if (canvas) {
const link = document.createElement('a');
link.download = 'stegasoo-channel-key-' + keyName.toLowerCase().replace(/\s+/g, '-') + '.png';
link.download = 'stego-channel-key-' + keyName.toLowerCase().replace(/\s+/g, '-') + '.png';
link.href = canvas.toDataURL('image/png');
link.click();
}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Password Reset - Stegasoo{% endblock %}
{% block title %}Password Reset - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,9 +1,9 @@
{% extends "base.html" %}
{% block title %}Settings — SooSeF Admin{% endblock %}
{% block title %}Settings — FieldWitness Admin{% endblock %}
{% block content %}
<h2><i class="bi bi-sliders me-2"></i>System Settings</h2>
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
System settings will be migrated from stegasoo's admin panel.
System settings will be migrated from fieldwitness.stego's admin panel.
</div>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}User Created - Stegasoo{% endblock %}
{% block title %}User Created - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Add User - Stegasoo{% endblock %}
{% block title %}Add User - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Manage Users - Stegasoo{% endblock %}
{% block title %}Manage Users - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Attest Image — SooSeF{% endblock %}
{% block title %}Attest Image — FieldWitness{% endblock %}
{% block content %}
<div class="row justify-content-center">
@@ -18,7 +18,7 @@
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>No identity configured.</strong> Generate one from the
<a href="/keys" class="alert-link">Keys page</a> or run <code>soosef init</code>.
<a href="/keys" class="alert-link">Keys page</a> or run <code>fieldwitness init</code>.
</div>
{% endif %}

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Attestation Log — SooSeF{% endblock %}
{% block title %}Attestation Log — FieldWitness{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Attestation Record — SooSeF{% endblock %}
{% block title %}Attestation Record — FieldWitness{% endblock %}
{% block content %}
<h2><i class="bi bi-file-earmark-check me-2"></i>Attestation Record</h2>
<p class="text-muted">Record ID: <code>{{ record_id }}</code></p>

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Attestation Created — SooSeF{% endblock %}
{% block title %}Attestation Created — FieldWitness{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Verify Image — SooSeF{% endblock %}
{% block title %}Verify Image — FieldWitness{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Verification Result — SooSeF{% endblock %}
{% block title %}Verification Result — FieldWitness{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}SooSeF{% endblock %}</title>
<title>{% block title %}FieldWitness{% endblock %}</title>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
<link href="{{ url_for('static', filename='vendor/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='vendor/css/bootstrap-icons.min.css') }}" rel="stylesheet">
@@ -13,7 +13,7 @@
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/" style="padding-left: 6px; margin-right: 8px;">
<strong>SooSeF</strong>
<strong>FieldWitness</strong>
</a>
{# Channel + Identity indicators #}
@@ -40,7 +40,7 @@
</li>
{% if not auth_enabled or is_authenticated %}
{# ── Stegasoo ── #}
{# ── Stego ── #}
<li class="nav-item">
<a class="nav-link nav-expand" href="/encode"><i class="bi bi-lock"></i><span>Encode</span></a>
</li>
@@ -51,8 +51,8 @@
<a class="nav-link nav-expand" href="/generate"><i class="bi bi-key"></i><span>Generate</span></a>
</li>
{# ── Verisoo ── #}
{% if has_verisoo %}
{# ── Attest ── #}
{% if has_attest %}
<li class="nav-item">
<a class="nav-link nav-expand" href="/attest"><i class="bi bi-patch-check"></i><span>Attest</span></a>
</li>
@@ -140,9 +140,9 @@
<footer class="py-4 mt-5">
<div class="container text-center text-muted">
<small>
SooSeF v{{ version }} — Soo Security Fieldkit
FieldWitness v{{ version }} — FieldWitness
<span class="mx-2">|</span>
<span class="text-muted">Stegasoo + Verisoo</span>
<span class="text-muted">Stego + Attest</span>
</small>
</div>
</footer>

View File

@@ -1,8 +1,8 @@
{% extends "base.html" %}
{% block title %}Source Drop Box — SooSeF{% endblock %}
{% block title %}Source Drop Box — FieldWitness{% endblock %}
{% block content %}
<h2><i class="bi bi-inbox me-2"></i>Source Drop Box</h2>
<p class="text-muted">Create time-limited upload links for sources who cannot install SooSeF.</p>
<p class="text-muted">Create time-limited upload links for sources who cannot install FieldWitness.</p>
<div class="card bg-dark mb-4">
<div class="card-body">

View File

@@ -1,8 +1,8 @@
{% extends "base.html" %}
{% block title %}Federation — SooSeF{% endblock %}
{% block title %}Federation — FieldWitness{% endblock %}
{% block content %}
<h2><i class="bi bi-diagram-3 me-2"></i>Federation</h2>
<p class="text-muted">Gossip-based attestation sync between SooSeF instances.</p>
<p class="text-muted">Gossip-based attestation sync between FieldWitness instances.</p>
<div class="row mb-4">
<div class="col-md-4">

View File

@@ -1,8 +1,8 @@
{% extends "base.html" %}
{% block title %}Keys — SooSeF{% endblock %}
{% block title %}Keys — FieldWitness{% endblock %}
{% block content %}
<h2><i class="bi bi-key me-2"></i>Key Management</h2>
<p class="text-muted">Manage Stegasoo channel keys and Verisoo Ed25519 identity.</p>
<p class="text-muted">Manage Stego channel keys and Attest Ed25519 identity.</p>
<div class="row g-4">
{# Channel Key #}
@@ -13,7 +13,7 @@
{% if keystore.has_channel_key %}
<p class="text-muted small">
Fingerprint: <code>{{ keystore.channel_fingerprint }}</code><br>
Used for Stegasoo deployment isolation.
Used for Stego deployment isolation.
</p>
{% else %}
<p class="text-muted small">No channel key configured.</p>
@@ -36,7 +36,7 @@
{% if keystore.has_identity %}
<p class="text-muted small">
Fingerprint: <code>{{ keystore.identity_fingerprint }}</code><br>
Used for Verisoo attestation signing.
Used for Attest attestation signing.
</p>
{% else %}
<p class="text-muted small">No identity configured.</p>

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Killswitch — SooSeF{% endblock %}
{% block title %}Killswitch — FieldWitness{% endblock %}
{% block content %}
<h2 class="text-danger"><i class="bi bi-exclamation-octagon me-2"></i>Emergency Killswitch</h2>
<p class="text-muted">Destroy all key material and sensitive data. This action is irreversible.</p>
@@ -9,7 +9,7 @@
<h5 class="card-title text-danger">Destruction Order</h5>
<ol class="text-muted small">
<li>Ed25519 identity keys (signing identity)</li>
<li>Stegasoo channel key (deployment binding)</li>
<li>Stego channel key (deployment binding)</li>
<li>Flask session secret (invalidates all sessions)</li>
<li>Auth database (user accounts)</li>
<li>Attestation log + index (provenance records)</li>

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Fieldkit Status — SooSeF{% endblock %}
{% block title %}Fieldkit Status — FieldWitness{% endblock %}
{% block content %}
<h2><i class="bi bi-speedometer2 me-2"></i>Fieldkit Status</h2>
<p class="text-muted">Security monitors and system health.</p>

View File

@@ -1,19 +1,19 @@
{% extends "base.html" %}
{% block title %}SooSeF — Soo Security Fieldkit{% endblock %}
{% block title %}FieldWitness — FieldWitness{% endblock %}
{% block content %}
<div class="text-center mb-5">
<h1 class="display-5 fw-bold">Soo Security Fieldkit</h1>
<h1 class="display-5 fw-bold">FieldWitness</h1>
<p class="lead text-muted">Offline-first security toolkit for field operations</p>
</div>
<div class="row g-4">
{# ── Stegasoo Card ── #}
{# ── Stego Card ── #}
<div class="col-md-6 col-lg-4">
<div class="card h-100 bg-dark border-secondary">
<div class="card-body">
<h5 class="card-title"><i class="bi bi-lock me-2 text-primary"></i>Encode</h5>
<p class="card-text text-muted">Hide encrypted messages in images or audio using Stegasoo's hybrid authentication.</p>
<p class="card-text text-muted">Hide encrypted messages in images or audio using Stego's hybrid authentication.</p>
<a href="/encode" class="btn btn-outline-primary btn-sm">Encode Message</a>
</div>
</div>
@@ -37,8 +37,8 @@
</div>
</div>
{# ── Verisoo Cards ── #}
{% if has_verisoo %}
{# ── Attest Cards ── #}
{% if has_attest %}
<div class="col-md-6 col-lg-4">
<div class="card h-100 bg-dark border-secondary">
<div class="card-body">
@@ -102,10 +102,10 @@
<i class="bi bi-image me-1"></i>DCT: {{ 'Available' if has_dct else 'Unavailable' }}
</span>
</div>
{% if has_verisoo %}
{% if has_attest %}
<div class="col-auto">
<span class="badge bg-success">
<i class="bi bi-patch-check me-1"></i>Verisoo: Active
<i class="bi bi-patch-check me-1"></i>Attest: Active
</span>
</div>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Login - Stegasoo{% endblock %}
{% block title %}Login - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Password Recovery - Stegasoo{% endblock %}
{% block title %}Password Recovery - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">
@@ -116,7 +116,7 @@
<div class="alert alert-warning mt-4 small">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Note:</strong> This will reset the admin password. If you don't have a valid recovery key,
you'll need to delete the database and reconfigure Stegasoo.
you'll need to delete the database and reconfigure Stego.
</div>
</div>
</div>
@@ -125,6 +125,6 @@
{% block scripts %}
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
<script>
StegasooAuth.initPasswordConfirmation('recoverForm', 'passwordInput', 'passwordConfirmInput');
StegoAuth.initPasswordConfirmation('recoverForm', 'passwordInput', 'passwordConfirmInput');
</script>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Regenerate Recovery Key - Stegasoo{% endblock %}
{% block title %}Regenerate Recovery Key - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">
@@ -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();
}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Setup - Stegasoo{% endblock %}
{% block title %}Setup - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">
@@ -12,7 +12,7 @@
</div>
<div class="card-body">
<p class="text-muted text-center mb-4">
Welcome to Stegasoo! Create your admin account to get started.
Welcome to Stego! Create your admin account to get started.
</p>
<form method="POST" action="{{ url_for('setup') }}" id="setupForm">
@@ -72,6 +72,6 @@
{% block scripts %}
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
<script>
StegasooAuth.initPasswordConfirmation('setupForm', 'passwordInput', 'passwordConfirmInput');
StegoAuth.initPasswordConfirmation('setupForm', 'passwordInput', 'passwordConfirmInput');
</script>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Recovery Key Setup - Stegasoo{% endblock %}
{% block title %}Recovery Key Setup - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">
@@ -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();
}

View File

@@ -1,17 +1,17 @@
{% extends "base.html" %}
{% block title %}About - Stegasoo{% endblock %}
{% block title %}About - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>About Stegasoo</h5>
<h5 class="mb-0"><i class="bi bi-info-circle me-2"></i>About Stego</h5>
</div>
<div class="card-body">
<p class="lead">
Stegasoo hides encrypted messages and files inside images using multi-factor authentication.
Stego hides encrypted messages and files inside images using multi-factor authentication.
</p>
<h6 class="text-primary mt-4 mb-3">Features</h6>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Decode Message - Stegasoo{% endblock %}
{% block title %}Decode Message - Stego{% endblock %}
{% block content %}
<style>
@@ -487,7 +487,7 @@
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
<script>
// ============================================================================
// MODE HINT - Dynamic text based on selected extraction mode
@@ -677,6 +677,6 @@ if (document.getElementById('modeDct')?.disabled) {
// LOADING STATE
// ============================================================================
Stegasoo.initFormLoading('decodeForm', 'decodeBtn', 'Decoding...');
Stego.initFormLoading('decodeForm', 'decodeBtn', 'Decoding...');
</script>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Encode Message - Stegasoo{% endblock %}
{% block title %}Encode Message - Stego{% endblock %}
{% block content %}
<style>
@@ -507,7 +507,7 @@
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
<script>
// ============================================================================
// MODE HINT - Dynamic text based on selected embedding mode

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Encode Success - Stegasoo{% endblock %}
{% block title %}Encode Success - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">
@@ -218,7 +218,7 @@ if (navigator.share && navigator.canShare) {
try {
await navigator.share({
files: [file],
title: 'Stegasoo Image',
title: 'Stego Image',
});
} catch (err) {
if (err.name !== 'AbortError') {

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Generate Credentials - Stegasoo{% endblock %}
{% block title %}Generate Credentials - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center" data-page="generate">
@@ -500,7 +500,7 @@
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/soosef.js') }}"></script>
<script src="{{ url_for('static', filename='js/fieldwitness.js') }}"></script>
<script src="{{ url_for('static', filename='js/generate.js') }}"></script>
{% if generated %}
<script>
@@ -508,7 +508,7 @@
const passphraseWords = '{{ passphrase|default("", true) }}'.split(' ').filter(w => w.length > 0);
function copyPin() {
Stegasoo.copyToClipboard(
Stego.copyToClipboard(
'{{ pin|default("", true) }}',
document.getElementById('pinCopyIcon'),
document.getElementById('pinCopyText')
@@ -516,7 +516,7 @@ function copyPin() {
}
function copyPassphrase() {
Stegasoo.copyToClipboard(
Stego.copyToClipboard(
'{{ passphrase|default("", true) }}',
document.getElementById('passphraseCopyIcon'),
document.getElementById('passphraseCopyText')
@@ -524,11 +524,11 @@ function copyPassphrase() {
}
function toggleMemoryAid() {
StegasooGenerate.toggleMemoryAid(passphraseWords);
StegoGenerate.toggleMemoryAid(passphraseWords);
}
function regenerateStory() {
StegasooGenerate.regenerateStory(passphraseWords);
StegoGenerate.regenerateStory(passphraseWords);
}
</script>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Tools - Stegasoo{% endblock %}
{% block title %}Tools - Stego{% endblock %}
{% block content %}
<div class="row justify-content-center">