Complete rebrand cleanup: remaining env vars and references

Fix STEGASOO_* env vars → FIELDWITNESS_* and VERISOO_* → FIELDWITNESS_*
across stego module, attest module, and frontends. Wire format
identifiers (VERISOO\x00 magic bytes, STEGASOO-Z: QR prefixes)
intentionally preserved for backwards compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee 2026-04-02 15:07:31 -04:00
parent 490f9d4a1d
commit 88f5571bf9
19 changed files with 66 additions and 66 deletions

View File

@ -34,9 +34,9 @@ src/fieldwitness/ Core library
__init__.py Package init, __version__ (0.2.0)
_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)
audit.py Append-only JSON-lines audit log (~/.fwmetadata/audit.jsonl)
cli.py Click CLI entry point (fieldwitness command)
paths.py All ~/.fieldwitness/* path constants (single source of truth, lazy resolution)
paths.py All ~/.fwmetadata/* 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
@ -179,7 +179,7 @@ Stego and Attest are inlined subpackages, not separate pip packages:
- **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 fieldwitness.stego -- it's a crash-safety boundary
- **All state under ~/.fieldwitness/** -- one directory to back up, one to destroy.
- **All state under ~/.fwmetadata/** -- 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
@ -208,10 +208,10 @@ Stego and Attest 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 (`~/.fieldwitness/`)
## Data directory layout (`~/.fwmetadata/`)
```
~/.fieldwitness/
~/.fwmetadata/
config.json Unified configuration (FieldWitnessConfig dataclass)
audit.jsonl Append-only audit trail (JSON-lines)
carrier_history.json Carrier reuse tracking database

View File

@ -48,7 +48,7 @@ fieldwitness init
fieldwitness serve
```
This creates the `~/.fieldwitness/` directory structure, generates an Ed25519 identity and
This creates the `~/.fwmetadata/` 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`.
@ -146,7 +146,7 @@ responses.
### Field Security (Fieldkit)
- **Killswitch** -- emergency destruction of all data under `~/.fieldwitness/`, ordered by
- **Killswitch** -- emergency destruction of all data under `~/.fwmetadata/`, 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"
@ -307,7 +307,7 @@ FieldWitness 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 ~/.fieldwitness/config.json
cp deploy/config-presets/high-threat.json ~/.fwmetadata/config.json
```
See [docs/deployment.md](docs/deployment.md) for the full deployment guide including
@ -317,7 +317,7 @@ security hardening, Kubernetes manifests, systemd services, and operational secu
## CLI Reference
All commands accept `--data-dir PATH` to override the default `~/.fieldwitness` directory,
All commands accept `--data-dir PATH` to override the default `~/.fwmetadata` directory,
and `--json` for machine-readable output.
```
@ -488,7 +488,7 @@ through Flask blueprints. Served by **Waitress** (production WSGI server) by def
## Configuration
FieldWitness loads configuration from `~/.fieldwitness/config.json`. All fields have sensible defaults.
FieldWitness loads configuration from `~/.fwmetadata/config.json`. All fields have sensible defaults.
`fieldwitness init` writes the default config file.
### Config fields
@ -522,7 +522,7 @@ FieldWitness loads configuration from `~/.fieldwitness/config.json`. All fields
| Variable | Description |
|---|---|
| `FIELDWITNESS_DATA_DIR` | Override the data directory (default: `~/.fieldwitness`). Enables portable USB mode and cover/duress directory naming |
| `FIELDWITNESS_DATA_DIR` | Override the data directory (default: `~/.fwmetadata`). Enables portable USB mode and cover/duress directory naming |
---
@ -582,10 +582,10 @@ deploy/ Deployment artifacts
config-presets/ Threat level presets (low/medium/high/critical)
```
### Data directory (`~/.fieldwitness/`)
### Data directory (`~/.fwmetadata/`)
```
~/.fieldwitness/
~/.fwmetadata/
config.json Unified configuration
audit.jsonl Append-only audit trail
carrier_history.json Carrier reuse tracking database
@ -612,7 +612,7 @@ Sensitive directories (`identity/`, `auth/`, `certs/`, and the root) are created
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 `~/.fieldwitness/`, including
**Killswitch priority.** The killswitch destroys all data under `~/.fwmetadata/`, 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

View File

@ -593,7 +593,7 @@ RestartSec=5
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/fieldwitness/.fieldwitness
ReadWritePaths=/home/fieldwitness/.fwmetadata
PrivateTmp=yes
[Install]

View File

@ -1602,7 +1602,7 @@ const Stego = {
// Webcam QR scanning for RSA key (v4.1.5)
document.getElementById('rsaQrWebcam')?.addEventListener('click', () => {
this.showQrScanner((text) => {
// Check for raw PEM or compressed format (STEGASOO-Z: prefix)
// Check for raw PEM or compressed format (legacy STEGASOO-Z: prefix)
const isRawPem = text.includes('-----BEGIN') && text.includes('KEY-----');
const isCompressed = text.startsWith('STEGASOO-Z:');
if (isRawPem || isCompressed) {
@ -1672,7 +1672,7 @@ const Stego = {
// Webcam QR scanning for RSA key (v4.1.5)
document.getElementById('rsaQrWebcam')?.addEventListener('click', () => {
this.showQrScanner((text) => {
// Check for raw PEM or compressed format (STEGASOO-Z: prefix)
// Check for raw PEM or compressed format (legacy STEGASOO-Z: prefix)
const isRawPem = text.includes('-----BEGIN') && text.includes('KEY-----');
const isCompressed = text.startsWith('STEGASOO-Z:');
if (isRawPem || isCompressed) {

View File

@ -325,7 +325,7 @@
<div class="alert alert-info mt-3 mb-0">
<i class="bi bi-info-circle me-2"></i>
This server is running in <strong>public mode</strong>.
Set <code>STEGASOO_CHANNEL_KEY</code> to enable server-wide channel isolation.
Set <code>FIELDWITNESS_CHANNEL_KEY</code> to enable server-wide channel isolation.
</div>
{% endif %}
</div>

View File

@ -33,7 +33,7 @@ from .crypto import verify_signature, load_public_key_from_bytes
# Configuration via environment
DATA_DIR = Path(os.environ.get("FIELDWITNESS_DATA_DIR", Path.home() / ".fieldwitness"))
BASE_URL = os.environ.get("VERISOO_BASE_URL", "https://attest.io")
BASE_URL = os.environ.get("FIELDWITNESS_BASE_URL", "https://attest.io")
app = FastAPI(
title="Attest",

View File

@ -686,8 +686,8 @@ def serve(host: str, port: int) -> None:
\b
ENVIRONMENT VARIABLES:
VERISOO_DATA_DIR Override data directory
VERISOO_BASE_URL Base URL for proof links (default: https://attest.io)
FIELDWITNESS_DATA_DIR Override data directory
FIELDWITNESS_BASE_URL Base URL for proof links (default: https://attest.io)
\b
SECURITY NOTES:

View File

@ -37,9 +37,9 @@ try:
has_jpegio_support,
calculate_dct_capacity,
)
HAS_STEGASOO = True
HAS_STEGO = True
except ImportError:
HAS_STEGASOO = False
HAS_STEGO = False
has_dct_support = lambda: False
has_jpegio_support = lambda: False
@ -50,7 +50,7 @@ except ImportError:
# Fixed public seed for Attest proof links
# This is intentionally public - anyone should be able to extract the proof link
VERISOO_SEED = b"attest"
ATTEST_SEED = b"attest"
# Base URL for proof links
DEFAULT_BASE_URL = "https://attest.io"
@ -250,7 +250,7 @@ def embed_proof_in_jpeg(
ImportError: If stego is not available
ValueError: If image is too small or embedding fails
"""
if not HAS_STEGASOO:
if not HAS_STEGO:
raise ImportError(
"DCT embedding requires stego. "
"Ensure stego is installed or available at ../stego"
@ -268,7 +268,7 @@ def embed_proof_in_jpeg(
stego_bytes, stats = embed_in_dct(
data=payload,
carrier_image=image_data,
seed=VERISOO_SEED,
seed=ATTEST_SEED,
output_format="jpeg",
color_mode="color",
)
@ -291,13 +291,13 @@ def extract_proof_from_jpeg(image_data: bytes) -> str | None:
Returns:
Proof URL string or None if not found/invalid
"""
if not HAS_STEGASOO:
if not HAS_STEGO:
return None
try:
payload = extract_from_dct(
stego_image=image_data,
seed=VERISOO_SEED,
seed=ATTEST_SEED,
)
# Validate it looks like a proof link
@ -327,7 +327,7 @@ def get_embed_method(image_path: Path) -> str:
suffix = image_path.suffix.lower()
if suffix in DCT_FORMATS:
if HAS_STEGASOO and has_jpegio_support():
if HAS_STEGO and has_jpegio_support():
return "dct"
else:
return "xmp" # Fallback to XMP if stego unavailable
@ -485,7 +485,7 @@ def extract_proof_link(image_path: Path) -> ExtractResult:
suffix = image_path.suffix.lower()
# Try DCT for JPEG
if suffix in DCT_FORMATS and HAS_STEGASOO:
if suffix in DCT_FORMATS and HAS_STEGO:
try:
image_data = image_path.read_bytes()
proof_link = extract_proof_from_jpeg(image_data)
@ -523,14 +523,14 @@ def extract_proof_link(image_path: Path) -> ExtractResult:
def can_embed_dct() -> bool:
"""Check if DCT embedding is available."""
return HAS_STEGASOO and has_jpegio_support()
return HAS_STEGO and has_jpegio_support()
def get_embed_capabilities() -> dict[str, Any]:
"""Get information about available embedding capabilities."""
return {
"dct_available": HAS_STEGASOO and has_dct_support(),
"jpeg_native": HAS_STEGASOO and has_jpegio_support(),
"dct_available": HAS_STEGO and has_dct_support(),
"jpeg_native": HAS_STEGO and has_jpegio_support(),
"xmp_available": True, # Always available
"supported_dct_formats": list(DCT_FORMATS) if can_embed_dct() else [],
"supported_xmp_formats": list(XMP_FORMATS | RAW_FORMATS),

View File

@ -199,11 +199,11 @@ class KeystoreManager:
def has_channel_key(self) -> bool:
"""Check if a channel key is configured."""
return bool(os.environ.get("STEGASOO_CHANNEL_KEY")) or self._channel_key_file.exists()
return bool(os.environ.get("FIELDWITNESS_CHANNEL_KEY")) or self._channel_key_file.exists()
def get_channel_key(self) -> str | None:
"""Get the channel key, or None if not configured."""
env_key = os.environ.get("STEGASOO_CHANNEL_KEY")
env_key = os.environ.get("FIELDWITNESS_CHANNEL_KEY")
if env_key:
return env_key
if self._channel_key_file.exists():
@ -246,7 +246,7 @@ class KeystoreManager:
# representation to back up, so we refuse rather than silently skip.
if not self._channel_key_file.exists():
raise KeystoreError(
"Channel key is set via STEGASOO_CHANNEL_KEY environment variable "
"Channel key is set via FIELDWITNESS_CHANNEL_KEY environment variable "
"and cannot be rotated through fieldwitness. Unset the variable and store "
"the key in the keystore first."
)

View File

@ -30,7 +30,7 @@ _PATH_DEFS: dict[str, tuple[str, ...]] = {
# on fragile filesystem mtime.
"IDENTITY_META": ("identity", "identity.meta.json"),
# Stego state
"STEGASOO_DIR": ("stego",),
"FIELDWITNESS_DIR": ("stego",),
"CHANNEL_KEY_FILE": ("stego", "channel.key"),
# Attest attestation storage
"ATTESTATIONS_DIR": ("attestations",),
@ -84,7 +84,7 @@ def ensure_dirs() -> None:
dirs = [
BASE_DIR,
__getattr__("IDENTITY_DIR"),
__getattr__("STEGASOO_DIR"),
__getattr__("FIELDWITNESS_DIR"),
__getattr__("ATTESTATIONS_DIR"),
__getattr__("CHAIN_DIR"),
__getattr__("AUTH_DIR"),

View File

@ -22,7 +22,7 @@ from .channel import (
validate_channel_key,
)
# Audio support — gated by STEGASOO_AUDIO env var and dependency availability
# Audio support — gated by FIELDWITNESS_AUDIO env var and dependency availability
from .constants import AUDIO_ENABLED, VIDEO_ENABLED
# Crypto functions
@ -87,7 +87,7 @@ else:
encode_audio = None
decode_audio = None
# Video support — gated by STEGASOO_VIDEO env var and ffmpeg + audio deps
# Video support — gated by FIELDWITNESS_VIDEO env var and ffmpeg + audio deps
if VIDEO_ENABLED:
from .decode import decode_video
from .encode import encode_video

View File

@ -45,7 +45,7 @@ from pathlib import Path
from typing import Literal
# Configure logging for API frontend
_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),
@ -53,7 +53,7 @@ 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",

View File

@ -32,7 +32,7 @@ PROJECT_CONFIG_DIR = Path("./config")
API_KEYS_FILE = "api_keys.json"
# Environment variable for API key (alternative to file)
API_KEY_ENV_VAR = "STEGASOO_API_KEY"
API_KEY_ENV_VAR = "FIELDWITNESS_API_KEY"
def _hash_key(key: str) -> str:

View File

@ -12,7 +12,7 @@ Use cases:
- Public instances: No channel key = compatible with any instance without a channel key
Storage priority:
1. Environment variable: STEGASOO_CHANNEL_KEY
1. Environment variable: FIELDWITNESS_CHANNEL_KEY
2. Config file: ~/.stego/channel.key or ./config/channel.key
3. None (public mode - compatible with any instance without a channel key)
@ -39,7 +39,7 @@ CHANNEL_KEY_LENGTH = 32 # Characters (excluding dashes)
CHANNEL_KEY_FORMATTED_LENGTH = 39 # With dashes
# Environment variable name
CHANNEL_KEY_ENV_VAR = "STEGASOO_CHANNEL_KEY"
CHANNEL_KEY_ENV_VAR = "FIELDWITNESS_CHANNEL_KEY"
# Config locations (in priority order)
CONFIG_LOCATIONS = [
@ -200,7 +200,7 @@ def get_channel_key() -> str | None:
Get the current channel key from environment or config.
Checks in order:
1. STEGASOO_CHANNEL_KEY environment variable
1. FIELDWITNESS_CHANNEL_KEY environment variable
2. ./config/channel.key file
3. ~/.stego/channel.key file

View File

@ -533,7 +533,7 @@ def audio_encode(
if not AUDIO_ENABLED:
raise click.UsageError(
"Audio support is disabled. Install audio extras (pip install stego[audio]) "
"or set STEGASOO_AUDIO=1 to force enable."
"or set FIELDWITNESS_AUDIO=1 to force enable."
)
from .audio_steganography import calculate_audio_lsb_capacity
@ -723,7 +723,7 @@ def audio_decode(
if not AUDIO_ENABLED:
raise click.UsageError(
"Audio support is disabled. Install audio extras (pip install stego[audio]) "
"or set STEGASOO_AUDIO=1 to force enable."
"or set FIELDWITNESS_AUDIO=1 to force enable."
)
from .decode import decode_audio
@ -819,7 +819,7 @@ def audio_info(ctx, audio):
if not AUDIO_ENABLED:
raise click.UsageError(
"Audio support is disabled. Install audio extras (pip install stego[audio]) "
"or set STEGASOO_AUDIO=1 to force enable."
"or set FIELDWITNESS_AUDIO=1 to force enable."
)
from .audio_steganography import calculate_audio_lsb_capacity
@ -964,7 +964,7 @@ def video_encode(
if not VIDEO_ENABLED:
raise click.UsageError(
"Video support is disabled. Install ffmpeg and audio extras, "
"or set STEGASOO_VIDEO=1 to force enable."
"or set FIELDWITNESS_VIDEO=1 to force enable."
)
from .encode import encode_video
@ -1137,7 +1137,7 @@ def video_decode(
if not VIDEO_ENABLED:
raise click.UsageError(
"Video support is disabled. Install ffmpeg and audio extras, "
"or set STEGASOO_VIDEO=1 to force enable."
"or set FIELDWITNESS_VIDEO=1 to force enable."
)
from .decode import decode_video
@ -1232,7 +1232,7 @@ def video_info(ctx, video):
if not VIDEO_ENABLED:
raise click.UsageError(
"Video support is disabled. Install ffmpeg and audio extras, "
"or set STEGASOO_VIDEO=1 to force enable."
"or set FIELDWITNESS_VIDEO=1 to force enable."
)
from .video_utils import calculate_video_capacity, get_video_info
@ -1713,7 +1713,7 @@ def info(ctx, full):
click.echo(json.dumps(info_data, indent=2))
else:
# Fastfetch-style output
click.echo(f"\033[1mSTEGASOO\033[0m v{__version__}")
click.echo(f"\033[1mFIELDWITNESS\033[0m v{__version__}")
click.echo("" * 36)
# Service status
@ -1819,7 +1819,7 @@ def channel_generate(ctx, save, save_user):
click.echo(f"Saved to: {path}")
else:
click.echo("To use this key:")
click.echo(f' export STEGASOO_CHANNEL_KEY="{key}"')
click.echo(f' export FIELDWITNESS_CHANNEL_KEY="{key}"')
click.echo()
click.echo("Or save to config:")
click.echo(" stego channel generate --save")

View File

@ -320,7 +320,7 @@ def detect_stego_mode(encrypted_data: bytes) -> str:
# Environment variables to enable/disable optional feature families.
# Values: "auto" (default — detect dependencies), "1"/"true" (force on),
# "0"/"false" (force off even if deps are installed).
# Pi builds or minimal installs can set STEGASOO_AUDIO=0 to stay image-only.
# Pi builds or minimal installs can set FIELDWITNESS_AUDIO=0 to stay image-only.
import os as _os
@ -370,8 +370,8 @@ def _resolve_feature(toggle: str | bool, dep_check: callable) -> bool:
return dep_check()
_audio_toggle = _parse_feature_toggle("STEGASOO_AUDIO")
_video_toggle = _parse_feature_toggle("STEGASOO_VIDEO")
_audio_toggle = _parse_feature_toggle("FIELDWITNESS_AUDIO")
_video_toggle = _parse_feature_toggle("FIELDWITNESS_VIDEO")
AUDIO_ENABLED: bool = _resolve_feature(_audio_toggle, _check_audio_deps)
VIDEO_ENABLED: bool = _resolve_feature(_video_toggle, _check_video_deps)

View File

@ -4,14 +4,14 @@ Stego Debugging Utilities
Debugging, logging, and performance monitoring tools.
Configuration:
STEGASOO_LOG_LEVEL env var controls log level:
FIELDWITNESS_LOG_LEVEL env var controls log level:
- Not set or empty: logging disabled (production default)
- DEBUG: verbose debug output (encode/decode flow, crypto params, etc.)
- INFO: operational messages (format detection, mode selection)
- WARNING: potential issues (fallback KDF, format transcoding)
- ERROR: operation failures
STEGASOO_DEBUG=1 is a shorthand for STEGASOO_LOG_LEVEL=DEBUG
FIELDWITNESS_DEBUG=1 is a shorthand for FIELDWITNESS_LOG_LEVEL=DEBUG
CLI: stego --debug encode ... (sets DEBUG level for that invocation)
@ -47,12 +47,12 @@ VALIDATION_ASSERTIONS = True # Enable runtime validation assertions
def _configure_from_env() -> bool:
"""Configure logging from environment variables. Returns True if debug enabled."""
# STEGASOO_DEBUG=1 is shorthand for DEBUG level
if os.environ.get("STEGASOO_DEBUG", "").strip() in ("1", "true", "yes"):
# FIELDWITNESS_DEBUG=1 is shorthand for DEBUG level
if os.environ.get("FIELDWITNESS_DEBUG", "").strip() in ("1", "true", "yes"):
_setup_logging(logging.DEBUG)
return True
level_str = os.environ.get("STEGASOO_LOG_LEVEL", "").strip().upper()
level_str = os.environ.get("FIELDWITNESS_LOG_LEVEL", "").strip().upper()
if level_str and level_str in _LEVEL_MAP:
_setup_logging(_LEVEL_MAP[level_str])
return level_str == "DEBUG"

View File

@ -314,7 +314,7 @@ def decode_audio(
if not AUDIO_ENABLED:
raise ExtractionError(
"Audio support is disabled. Install audio extras (pip install stego[audio]) "
"or set STEGASOO_AUDIO=1 to force enable."
"or set FIELDWITNESS_AUDIO=1 to force enable."
)
from .audio_utils import detect_audio_format, transcode_to_wav
@ -434,7 +434,7 @@ def decode_video(
if not VIDEO_ENABLED:
raise ExtractionError(
"Video support is disabled. Install video extras and ffmpeg, "
"or set STEGASOO_VIDEO=1 to force enable."
"or set FIELDWITNESS_VIDEO=1 to force enable."
)
from .video_utils import detect_video_format

View File

@ -382,7 +382,7 @@ def encode_audio(
if not AUDIO_ENABLED:
raise AudioError(
"Audio support is disabled. Install audio extras (pip install stego[audio]) "
"or set STEGASOO_AUDIO=1 to force enable."
"or set FIELDWITNESS_AUDIO=1 to force enable."
)
from .audio_utils import detect_audio_format, transcode_to_wav
@ -485,7 +485,7 @@ def encode_video(
if not VIDEO_ENABLED:
raise VideoError(
"Video support is disabled. Install video extras and ffmpeg, "
"or set STEGASOO_VIDEO=1 to force enable."
"or set FIELDWITNESS_VIDEO=1 to force enable."
)
from .video_utils import detect_video_format