Added sections for binary hardening verification, runtime verification, and deployment footprint details.
13 KiB
ChronoSeal Deployment Guide
ChronoSeal is intended to run as a small Unix daemon behind TLS, with static browser assets served either by the daemon or by the same protected origin. This guide covers native, service, and container deployment.
Deployment Model
Typical production topology:
Internet
|
v
TLS reverse proxy
|
v
chronoseal daemon on 127.0.0.1:3000
|
v
sqlite-in-disk or valkey storage
For local evaluation, the daemon can bind directly to 0.0.0.0:3000 or 127.0.0.1:3000.
Requirements
| Tool | Minimum | Purpose |
|---|---|---|
| Rust | 1.87 stable | Build server and shared crates |
wasm32-unknown-unknown target |
current stable | Compile WASM runtime |
wasm-pack |
0.13 | Generate browser WASM package |
| systemd | 248+ | Native service management |
| Docker | 24.x | Optional container image |
| Docker Compose | 2.x | Optional local orchestration |
Install Rust from rustup, then install the WASM tooling:
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
Build
Use the repository build script:
bash scripts/build.sh
The script:
- Builds
wasm/withwasm-pack build --target web --release. - Replaces
frontend/pkgwith the generated package. - Builds the release daemon binary.
Manual equivalent:
wasm-pack build wasm --target web --release
rm -rf frontend/pkg
mv wasm/pkg frontend/pkg
cargo build -p chronoseal-server --bin chronoseal --release
Release binary:
target/release/chronoseal
Binary Hardening Verification
Before packaging or deploying ChronoSeal, verify that the release binary includes the expected platform hardening protections.
Security Inspection
Inspect the release binary with checksec:
checksec file target/release/chronoseal
Expected protections:
Full RELRO
Stack Canary Found
NX enabled
PIE Enabled
No RPATH
No RUNPATH
These mitigations help reduce the impact of memory corruption vulnerabilities and runtime exploitation.
Stripped Production Binary
To verify symbol reduction and release artifact quality:
strip target/release/chronoseal -o chronoseal.stripped
nm -D chronoseal.stripped | wc -l
A stripped production binary should expose only a small dynamic symbol set.
Check for remaining debug sections:
readelf -S chronoseal.stripped | grep debug
Production artifacts should not contain .debug_* sections.
Source Path Disclosure
Rust release builds may embed local source paths from the build environment.
To reduce path disclosure:
RUSTFLAGS="--remap-path-prefix=$HOME=~" \
cargo build --release
or:
RUSTFLAGS="--remap-path-prefix=$(pwd)=." \
cargo build --release
Recommended release profile:
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
strip = "symbols"
Runtime Verification
Start the daemon locally:
./chronoseal run --bind 127.0.0.1:8080
Expected startup output:
INFO chronoseal daemon started bind=127.0.0.1:8080
Verify core endpoints:
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/stats
curl http://127.0.0.1:8080/metrics
Successful responses confirm that:
- configuration loading succeeded
- storage initialization completed
- HTTP listeners are active
- observability endpoints are operational
PID File Permissions
When running as an unprivileged user, writing directly to /run may fail:
could not write PID file
Permission denied
For local development:
chronoseal run --pid-file /tmp/chronoseal.pid
For production systemd deployments, prefer:
RuntimeDirectory=chronoseal
and:
/run/chronoseal/chronoseal.pid
managed by systemd.
Additional Validation
Inspect runtime dependencies:
ldd target/release/chronoseal
Verify ELF program headers:
readelf -l target/release/chronoseal
Look for:
GNU_RELRO
GNU_STACK
Confirm binary size:
ls -lh target/release/chronoseal
These checks should be performed before publishing release artifacts, container images, or distribution packages.
Native Install
The installer builds, installs, enables, and starts the service:
sudo bash scripts/install.sh
Installer actions:
- create the
chronosealsystem user if missing - build WASM and server artifacts
- install
target/release/chronosealto/usr/local/bin/chronoseal - copy
frontend/to/opt/chronoseal/frontend - install
chronoseal.serviceto/etc/systemd/system/chronoseal.service - reload systemd
- enable and start the service
Verify:
sudo systemctl status chronoseal
chronoseal status --format json
chronoseal health
sudo journalctl -u chronoseal -f
Running Without Install
For local development:
bash scripts/build.sh
cargo run -p chronoseal-server --bin chronoseal -- run \
--bind 127.0.0.1:3000 \
--frontend-dir frontend
Probe the daemon:
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/stats
curl http://127.0.0.1:3000/metrics
Configuration
ChronoSeal resolves configuration in this order:
- CLI flags
CHRONOSEAL_*environment variables- TOML config file
- built-in defaults
Default config discovery:
CHRONOSEAL_CONFIG, if it points to an existing file/etc/chronoseal/config.toml$XDG_CONFIG_HOME/chronoseal/config.toml~/.config/chronoseal/config.toml
Validate effective configuration:
chronoseal config check --format yaml
Example:
bind = "127.0.0.1:3000"
db_type = "sqlite-in-disk"
pid_file = "/run/chronoseal.pid"
db_path = "/var/lib/chronoseal/chronoseal.sqlite"
frontend_dir = "/usr/share/chronoseal/frontend"
log_file = "/var/log/chronoseal/chronoseal.jsonl"
heartbeat_min_interval_ms = 12000
heartbeat_max_interval_ms = 25000
expiration_minutes = 30
rate_limit_count = 5
rate_limit_window_secs = 10
max_timestamp_drift_ms = 30000
min_mouse_total_dist = 10.0
max_mouse_avg_speed = 2.0
min_pause_count = 1
require_mouse_activity = true
gene_size = 512
mutation_rounds = 4
Storage Backends
| Backend | db_type |
Use case |
|---|---|---|
| SQLite memory | sqlite-in-memory |
ephemeral local or stateless deployment |
| SQLite disk | sqlite-in-disk |
persisted session continuity across restarts |
| Valkey | valkey |
external session storage |
For disk persistence:
sudo mkdir -p /var/lib/chronoseal
sudo chown -R chronoseal:chronoseal /var/lib/chronoseal
For Valkey / Redis:
ChronoSeal expects a running Valkey or Redis instance when db_type is set to valkey.
1. Installing Valkey or Redis
To install Valkey (the recommended open-source option) or Redis on Linux:
- Valkey (Debian/Ubuntu):
sudo apt-get install -y valkey-server - Redis (Debian/Ubuntu):
sudo apt-get install -y redis-server
2. Local Setup and Startup
By default, ChronoSeal searches for Valkey/Redis on 127.0.0.1:6666.
You can start a local instance manually:
# Start Valkey on port 6666
valkey-server --port 6666 --bind 127.0.0.1
# Or start Redis on port 6666
redis-server --port 6666 --bind 127.0.0.1
Or run it via Docker:
# Run Valkey container mapping host port 6666 to container port 6379
docker run -d --name chronoseal-valkey -p 6666:6379 valkey/valkey:latest
3. Service Configuration
Configure the environment variables to point ChronoSeal to your instance:
export CHRONOSEAL_DB_TYPE=valkey
export CHRONOSEAL_VALKEY_ADDR=127.0.0.1:6666
Providing Credentials & SSL/TLS
If your Valkey/Redis server requires authentication or secure TLS/SSL, include them directly in the CHRONOSEAL_VALKEY_ADDR connection URL:
- Password Only:
export CHRONOSEAL_VALKEY_ADDR=redis://:your_password@127.0.0.1:6666 - Username & Password:
export CHRONOSEAL_VALKEY_ADDR=redis://your_username:your_password@127.0.0.1:6666 - Secure Connection (SSL/TLS): Use the
rediss://scheme prefix:export CHRONOSEAL_VALKEY_ADDR=rediss://your_username:your_password@secure-valkey-host.example.com:6379
If Valkey connection setup fails, the current implementation logs a warning and falls back to in-memory SQLite.
systemd
The supplied service file is intended as the baseline unit. Keep the daemon under a dedicated user and restrict filesystem access to the paths it needs.
Useful commands:
sudo systemctl daemon-reload
sudo systemctl enable --now chronoseal
sudo systemctl restart chronoseal
sudo systemctl status chronoseal
sudo journalctl -u chronoseal -f
Recommended hardening properties include:
NoNewPrivileges=truePrivateTmp=trueProtectSystem=strictProtectHome=trueProtectKernelTunables=trueProtectKernelModules=trueProtectControlGroups=trueMemoryDenyWriteExecute=trueRestrictRealtime=trueRestrictSUIDSGID=trueSystemCallArchitectures=native
Any hardening must still allow access to:
- the binary
- frontend assets
- PID file directory
- optional log file directory
- SQLite database directory, if using
sqlite-in-disk
Reverse Proxy and TLS
ChronoSeal should be served over HTTPS in production. Terminate TLS at a reverse proxy or load balancer and proxy to the local daemon.
Minimal nginx example:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
proxy_read_timeout 35s;
proxy_send_timeout 10s;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
Keep /init, /hb, and frontend assets on the same origin when possible. If you split origins, configure CORS and cookie/application policy deliberately.
Docker
Build and run:
bash scripts/build.sh
docker compose up -d --build
The Compose file exposes port 3000.
curl http://127.0.0.1:3000/health
The Dockerfile copies frontend/ from the working tree. Build frontend/pkg before building the image when the browser WASM runtime is required inside the container.
Observability
CLI:
chronoseal status --format json
chronoseal health
chronoseal stats --format json
chronoseal metrics
HTTP:
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/stats
curl http://127.0.0.1:3000/metrics
Prometheus metrics:
chronoseal_sessionschronoseal_expired_sessionschronoseal_max_chain_length
Logging
Use info-level logs for production:
CHRONOSEAL_LOG=info chronoseal run
or with systemd:
sudo systemctl edit chronoseal
Avoid debug logging in production because internal identifiers may be written to logs.
Production Checklist
- Build
frontend/pkgbefore packaging. - Serve ChronoSeal traffic over HTTPS.
- Bind the daemon to localhost behind a reverse proxy unless direct exposure is required.
- Use a dedicated service user.
- Keep debug logs disabled.
- Choose storage intentionally:
sqlite-in-memory,sqlite-in-disk, orvalkey. - Protect SQLite and log directories with correct ownership.
- Monitor
/health,/stats, and/metrics. - Verify
chronoseal config checkafter environment or config changes.
Runtime Footprint
ChronoSeal is intentionally designed to maintain a small deployment footprint while providing browser attestation, cryptographic verification, session continuity, and WASM execution capabilities.
Typical v1.0.2 release artifact sizes:
| Component | Approximate Size |
|---|---|
Native daemon (chronoseal) |
~9.1 MiB |
Browser runtime (chronoseal_wasm.wasm) |
~728 KiB |
WASM static library (libchronoseal_wasm.rlib) |
~188 KiB |
Example:
chronoseal
9501232 bytes
≈ 9.06 MiB
chronoseal_wasm.wasm
745569 bytes
≈ 728 KiB
These compact artifact sizes help:
- reduce deployment overhead
- minimize container image growth
- improve cold-start performance
- reduce browser download size
- simplify edge and self-hosted deployments
ChronoSeal intentionally avoids heavyweight runtime dependencies and large browser frameworks, allowing the complete attestation stack to remain compact while preserving functionality.
## v1.0.2 Deployment Notes
- Containers run as a dedicated non-root user.
- Reverse proxies should forward X-Forwarded-For or X-Real-IP.
- Security headers are enabled by default.